native_liquid_glass 0.2.5
native_liquid_glass: ^0.2.5 copied to clipboard
Liquid Glass iOS platform views for Flutter using native UIKit components.
Changelog #
0.2.5 #
LiquidGlassContainer #
- Add
LiquidGlassConfig.backgroundColor— a solid color drawn inside the same shape but behind the glass material. Two complementary effects:- Density: the alpha channel controls how dense the glass reads.
Color(...).withValues(alpha: 0.4)gives a denser pill than the bare-glass default;alpha: 1.0produces an effectively solid colored surface with the glass material's highlight + shadow on top. Apple's iOS 26GlassAPI doesn't expose a density/intensity knob, so this controlled backdrop is the supported way to thicken the visible material. - Stable color: fixes the "colors below the glass appear inverted as I scroll" issue on iOS 26. The Liquid Glass material runs an adaptive-contrast pass that samples the brightness of whatever's behind it and inverts the material's tone for legibility — over varying Flutter content this can read as flickering. With
backgroundColorset, the glass refracts your fixed backdrop instead and stays stable.
- Density: the alpha channel controls how dense the glass reads.
- iOS implementation: rendered as
shape.fill(color)inside a.background { ... }modifier applied after.glassEffect(...), so the z-order isbackdrop → glass material → border overlay → Flutter child. The backdrop uses the sameBuiltInGlassShape/AnimatableCustomPathShapereference as the glass fill, so it animates with shape changes (corner radius, custom-path morph) underanimateChanges: true.
0.2.4 #
Custom border #
- Add new
LiquidGlassBorderclass (shared) — a stroked outline that follows the widget's underlying shape (capsule, rounded rect, circle, or custom path). Exposescolorandwidth. Exported frompackage:native_liquid_glass/native_liquid_glass.dart. LiquidGlassContainer: newLiquidGlassConfig.borderfield. Border traces every supported shape including animatable custom paths (the stroke animates with the shape via the sameanimatableDataused by the glass fill).LiquidGlassButton: newborderwidget property on both constructors (text + icon-only). Stroke followsborderRadius(capsule when null, rounded rect otherwise).LiquidGlassToolbar: newborderwidget property. Applied per capsule so every pill in a split toolbar gets the same outline.- iOS side: border renders as
shape.stroke(color, lineWidth:)inside an.overlay { ... }after.glassEffect(...), so the stroke sits on top of the glass material and matches the fill geometry exactly.
LiquidGlassContainer #
- Fix: re-added the
GlassEffectContainer(spacing: 0)wrapper around the container's SwiftUI body. This was claimed in the 0.2.0 CHANGELOG but the actual wrapping was missing from the shipped code, with three visible consequences: (a)Glass.clearrendered as a frosted white panel in light mode instead of the proper translucent material, (b)glassEffectUnion(id:)/glassEffectID(_:)— applied viaapplyLiquidGlassContainerModifiers— silently did nothing because they require aGlassEffectContainerancestor, and (c) interactive glass under-rendered. All three now work as documented. - Defensive:
hostingController.view.clipsToBounds = false(andlayer.masksToBounds = false) so the glass material's subtle drop-shadow doesn't clip at the host-view boundary (matches the toolbar's existing treatment). - Doc: clarified that
glass.interactive()on the native side is effectively dead because theUiKitViewis wrapped inIgnorePointerto let Flutter child widgets inside the container still receive taps. Touches never reach the native glass view, so the visual press feedback is produced Flutter-side viaSpringBuilder+Transform.scale. The.interactive()call is kept anyway because it subtly adjusts the baseline material, keeping the rendered look consistent withLiquidGlassButton/LiquidGlassToolbar.
0.2.3 #
LiquidGlassButton #
- Fix: the Dart-side wrap-content estimator (
_estimateWrapContentSize) was measuring text with a hardcodedTextStyle(fontSize: 17, fontWeight: .w600)and ignoringwidget.labelTextStyle. That mismatched what the nativeUIButtonactually renders when the caller passes a scaledlabelTextStyle(e.g. a ScreenUtil-scaled style on iPad) — the button would sit at a stale estimated width until the asyncgetIntrinsicSizeround-trip caught up, which in timing-sensitive layouts looked like the button was "frozen" at the wrong width. Estimator now resolveslabelTextStyle.fontSize/.fontWeight/.fontFamily/.letterSpacingbefore measuring, falling back to the 17pt / semibold iOS default only when the caller omits them. - Deprecated:
LiquidGlassButton.shrinkWrap. The field was never wired into layout — it's a no-op — but was documented as functional, which was misleading. Actual wrap-content behavior comes from leavingwidth: null(triggers anUnconstrainedBoxinternally). Now@Deprecatedwith the correct guidance; retained for source compatibility.
SVG asset icons #
- Fix:
NativeLiquidGlassIcon.asset(...)no longer crashes the app when the SVG has a wide viewBox (e.g.0 0 1000 1000) being rendered into a small icon target (e.g. 20pt). The button and toolbar decoders previously calledsvgImage.size = CGSize(width: iconSize, height: iconSize)before reading.uiImage, which triggers an SVGKit rendering crash at very small scale factors with certain SVG content (transforms, gradients, path numerics). Both decoders now let SVGKit render at the SVG's natural viewBox size and scale down with UIKit (matching what the tab bar decoder already does), plus gate on the rendered image having a non-zero size.
LiquidGlassToolbar #
heightnow maps 1:1 to the visible glass bar height. Reverted the 12pt vertical overflow padding added in 0.2.0 — the outer widget footprint now equalswidget.heightwith no implicit margin. Setting a smallheight(e.g. 32) actually shrinks the bar instead of just shrinking the surrounding padding.- Dropped the implicit 44pt minimum height on toolbar items (only the horizontal 44pt floor is kept for a comfortable tap target), so heights below 44 truly shrink the bar.
- Each capsule now snaps to an explicit
height(from the Flutter-suppliedwidget.height) instead of relying on.frame(maxHeight: .infinity), which didn't always force the full height through every SwiftUI layout path. - Add
itemSpacingparameter (default8, in points) — the horizontal gap between adjacent items. Implemented asitemSpacing / 2of horizontal padding per item, soitemSpacingis both the gap between adjacent items and the inset from each end of the capsule. - Add
paddingparameter (EdgeInsetsGeometry, defaultEdgeInsets.zero) applied inside each glass capsule (CSS-style padding of the capsule container), so the pill grows around its items rather than inserting an outer margin around the widget. In wrap-content mode the widget still wraps the capsule(s) tightly. Horizontal padding is the typical use; vertical padding shrinks the item content area while the capsule keepsheight. SupportsEdgeInsetsDirectionalfor RTL. - Fix: wrap-content (
width: null) no longer silently swells to the parent's width when the toolbar sits inside a tight-horizontal parent (e.g.Column(crossAxisAlignment: .stretch)or aPaddinginside one). Flutter clamps a child's preferred size up to the parent's tight constraint; that left theUiKitViewwider than the capsule(s) and rendered the leftover space as ifpadding/itemSpacinghad leaked outside the glass pill. The widget now wraps itself inUnconstrainedBox(constrainedAxis: Axis.vertical, alignment: Alignment.center)in wrap-content mode, so the bar shrinks to the estimated content width and centers within its parent slot (matching Apple's iOS 26 floating-toolbar visual). Passwidth: double.infinityfor explicit fill-parent. width: nullnow auto-promotes to fill-parent when [items] contains a flexibleLiquidGlassToolbarSpacer. Without this, a flex spacer in wrap-content mode silently collapsed to0and left the split capsules stuck together at one edge — almost never what a caller using a flex spacer wants. With a flex spacer in items, the toolbar now fills the parent's width so the spacer can distribute the capsule groups (the iOS 26 split-toolbar pattern). Without a flex spacer, wrap-content behavior is unchanged.- Behavior change:
LiquidGlassToolbarSpacer(flexible: false, width: X)now also splits the toolbar into separate capsules (with anX-point fixed gap between them), matching the behavior of flexible spacers. Previously fixed spacers stayed inside one capsule as an in-pill gap; now both spacer kinds produce the iOS 26 split-toolbar pattern. Theflexibleflag controls only whether the gap between capsules is variable (Spacer()) or a fixed width. - Fix: the leading capsule in a split toolbar (items separated by a flexible
LiquidGlassToolbarSpacer) is no longer clipped/invisible. EachToolbarGroupCapsulenow wraps its own.glassEffect(.regular.interactive(), in: Capsule())in a dedicatedGlassEffectContainer(spacing: 0). A single shared outer container was merging sibling pills into one fused surface and clipping the leading one; omitting the container entirely leftGlass.interactive()without the compositing context it needs to render fully (which is why other interactive-glass widgets likeLiquidGlassButtonalways pair.interactive()with aGlassEffectContainer). Per-capsule containment gives each pill an independent, fully-rendered glass surface.
LiquidGlassButton #
- Fix: icon-only buttons with
size: nulland text buttons withwidth: nullno longer swell to the parent's width when placed inside a tight-horizontal parent. SameUnconstrainedBox(constrainedAxis: Axis.vertical)treatment as the toolbar — this was observable on iPad where layout contexts more often pass tight horizontal constraints. Pass an explicitsize/width(ordouble.infinity) to opt out.
0.2.0 #
SVG Path Utility #
- Add
SvgPathExtensiononStringfor converting SVG path data into FlutterPathorLiquidGlassConfig.customPathops. toPath()/toPathScaled({viewBox, target})— parse SVG path data into a FlutterPath, with optional scaling.toLiquidGlassPath()— parse directly intoList<LiquidGlassPathOp>forLiquidGlassConfig.customPath.toLiquidGlassPathScaled({viewBox, target})— handles non-zero-origin viewBoxes (e.g."1 0 24 226") by translating then scaling every coordinate.- Accepts paths with leading whitespace, newlines, comments, or stray characters (everything before the first
M/mis stripped). - Quadratic Béziers are normalized to cubic Béziers to match the native iOS path rendering.
- Add
path_parsing: ^1.1.0as a direct dependency.
LiquidGlassContainer #
- Wrap SwiftUI glass content in
GlassEffectContainersoGlass.clearrenders with its proper translucent appearance in light mode instead of falling back to a frosted white panel, and soglassEffectUnionId/glassEffectIdmodifiers work as documented. - Press feedback on
interactive: truenow scales up to 1.04 (was: down to 0.96) so it visually mirrors the "spring out" aesthetic of Apple's nativeGlass.interactive()used byLiquidGlassButtonandLiquidGlassToolbar. The container can't use native.interactive()directly (itsUiKitViewisIgnorePointer-wrapped so Flutter child widgets inside can still receive taps), so this keeps the press-response visually consistent across the plugin. SameLiquidGlassSpring.interactive()preset as before.
LiquidGlassButton #
LiquidGlassButton.iconnow wraps its content whensizeis not provided, matching Flutter button semantics. The default forsizechanged from50tonull; when null, the button is sized fromiconSizeplus a minimum 44pt touch target, then refined to the native intrinsic size reported by the platform view. Breaking for callers that relied on the implicitsize: 50default — passsize: 50explicitly to preserve the old behavior.- The native
getIntrinsicSizeround-trip now runs for icon-only buttons too (previously only text buttons requested it), so icon buttons with nullsizesettle to the exact size Apple's Liquid Glass rendering prefers.
LiquidGlassTabBar #
- Fix: per-tab
selectedItemColor(fromLiquidGlassTabItem.selectedItemColor) no longer reverts to the globalselectedItemColorafter a Flutter navigation push/pop. The tab bar'sUITabBarAppearance.selectedcolors are now kept in sync with the currently-applied per-tab tint, and the tint is re-applied when the view re-attaches to a window.
LiquidGlassToolbar #
- On iOS 26+ the toolbar is now rendered as a SwiftUI
HStackwith.glassEffect(.regular, in: Capsule())(hosted viaUIHostingController).heightnow actually resizes the visible Liquid Glass bar — previously the underlyingUIToolbarlocked its glass rendering to the system-intrinsic 44pt and any extra height became transparent padding. - Each run of items between flexible
LiquidGlassToolbarSpacers is now rendered as its own independent glass capsule, matching the iOS 26 split-toolbar pattern (multiple floating pill bars side-by-side). Fixed spacers stay inside their group as internal gaps. - Press feedback now uses Apple's native
Glass.regular.interactive()on each capsule — the same mechanismLiquidGlassButtonuses. iOS 26's system handles the spring-out + tint response internally, so behavior (timing, scale, damping) is identical acrossLiquidGlassButton,LiquidGlassButtonGroup, andLiquidGlassToolbar. Sibling capsules in a split toolbar are unaffected when one is pressed. - Fix:
LiquidGlassToolbarItem.tintColornow actually colors the item's icon/text. Previously.tint(...)was applied to the item'sButton, but our customButtonStyleonly re-emitsconfiguration.label, so the tint never reached theImage/Text. Tint is now applied directly via.foregroundStyle(color)on the visible element. - Breaking: removed
LiquidGlassToolbarItem.styleand theLiquidGlassToolbarItemStyleenum. Text items now render withLiquidGlassToolbar.labelTextStyle's weight (or.regularif none supplied) — there's no longer a.donebold variant. If you need bold text for a specific item, pass alabelTextStyleon the whole toolbar. - The widget now reserves vertical overflow padding so the capsule's drop shadow and spring scale-down are not clipped. The visible bar still honors the supplied
height; only the outer widget footprint grows to accommodate the overflow. - Item spacing tightened to match UIToolbar's visual rhythm (minimum 44pt hit targets with 4pt inter-item padding instead of the previous 12pt HStack gap).
- Add optional
widthparameter. When null (default), the toolbar wraps its content — width is estimated from item sizes (TextPainterfor labels,iconSizefor icons, fixed spacer widths, plus glass overflow padding). Passdouble.infinityor wrap inExpandedfor full-parent-width behavior. Flexible spacers only expand when an explicit ordouble.infinitywidth is provided; in wrap-content mode they collapse to 0. - On iOS 15–25 the existing
UIToolbarpath is kept as a fallback (still constrained to 44pt, which matches the non-Liquid-Glass system behavior).
0.1.0 #
Spring Animation System #
- Add
LiquidGlassSpringwith four presets:bouncy,snappy,smooth,interactive. - Add
SingleSpringControllerandOffsetSpringControllerfor imperative spring-driven values. - Add
SpringBuilder,VelocitySpringBuilder, andOffsetSpringBuilderdeclarative widgets. - All builders respect
MediaQuery.disableAnimationsOffor accessibility (reduce motion).
Custom Shape Support for LiquidGlassContainer #
- Add
LiquidGlassEffectShape.customfor arbitrary glass container shapes. - Add
LiquidGlassPathOpsealed class (moveTo,lineTo,cubicTo,quadTo,close) for defining custom paths. - Add
customPathandcustomPathSizetoLiquidGlassConfig— coordinates are in your SVG/design space, the native side scales automatically. - Native
CustomPathShape(SwiftUIShape) renders the custom path on iOS 26+.
Animated Config Transitions #
- Add
animateChangesproperty toLiquidGlassContainer. - When enabled, shape/effect/tint/cornerRadius changes animate with a native SwiftUI spring transition.
- Refactored native side from view recreation to
ObservableObjectViewModel — the SwiftUI view stays alive across config updates. - Built-in shape transitions (rect/capsule/circle) use
Animatablecorner radius interpolation. - Custom shape transitions use
AnimatableCustomPathShapewith control point morphing viaVectorArithmetic. - Built-in to/from custom transitions use opacity crossfade.
Interactive Container #
LiquidGlassContainernow shows a spring press animation (scale down/bounce back) whenconfig.interactiveis true, matchingLiquidGlassButtonbehavior.- Add
onTapcallback toLiquidGlassContainer— uses Flutter's gesture system so child widgets receive touches normally. - Native
UiKitViewis wrapped inIgnorePointer; all gesture handling is Flutter-side.
Container Sizing #
LiquidGlassContainernow wraps its child size whenwidth/heightare omitted, matching FlutterContainerbehavior.- Native glass view uses
Positioned.fillto match the child-determined size.
0.0.3 #
- Add Swift Package Manager (SPM) support for iOS.
0.0.2 #
- Switch iOS tab bar implementation from custom SwiftUI rendering to system
UITabBarController. - Rely on iOS system Liquid Glass appearance on iOS 26+ instead of custom
.glassEffect()drawing. - Remove unsupported action-button and custom-shape parameters from
LiquidGlassTabBarAPI. - Add supported native customization: selected color and item positioning/spacing/width.
- Remove
backgroundColorandshadowColor; tab bar background and shadow visuals are now system-driven. - Remove
unselectedItemColorparameter so unselected item appearance is system-driven by iOS Liquid Glass behavior. - Add iOS tab badge support per item (
iosBadgeValue). - Remove
iosBadgeColorandiosBadgeTextColor; badge styling is now system-driven. - Add three icon source options for
LiquidGlassTabItem:sfSymbolName,iconData, andassetIconPath(plus selected variants). - Rename icon parameters to meaningful source-based names while keeping deprecated aliases (
icon,activeIcon,iosSystemImage,iosActiveSystemImage). - Add reusable static synchronous utility
NativeLiquidGlassUtils.isDeviceQualifiedForLiquidGlassWidget()for iOS 26+ qualification checks before rendering native widgets (noawaitrequired). - Add native customization for tab icon size via
LiquidGlassTabBar.iconSize. - Add per-item icon size override via
LiquidGlassTabItem.iconSize. - Add native label typography customization via
LiquidGlassTabBar.labelTextStyle(font size/weight/family/letter spacing). - Add per-item selected color override via
LiquidGlassTabItem.selectedItemColor. - Add SVG decoding support for
assetIconPathandselectedAssetIconPathon iOS. - Add optional iOS action button support via
iosActionButtonandonActionButtonPressed. - Remove iOS search feature and related callbacks (
iosShowSearchButton,onSearchButtonPressed,onSearchQueryChanged,onSearchSubmitted,onSearchDismissed). - Add
LiquidGlassButtonwith iOS native platform-view rendering and Flutter fallback. - Add
LiquidGlassIconButtonwith iOS native platform-view rendering and Flutter fallback. - Add three icon source options for buttons (
sfSymbolName,iconData,assetIconPath) with iOS native source-priority resolution. - Add native button visual parameters:
iconColor,foregroundColor,glassTintColor,imagePadding, andglassInteractive. - Use default UIKit button configuration APIs for button rendering; on iOS/iPadOS 26+ use
UIButton.Configuration.prominentGlass()with interactiveUIGlassEffect. - Performance:
LiquidGlassButtonGroupicon payload resolution is now gated by an icon signature — payloads are not re-resolved unless icons actually change between widget rebuilds. - Performance:
LiquidGlassButtonGroupresolves icon payloads for all buttons concurrently withFuture.waitinstead of sequentially. - Performance:
LiquidGlassButtonskipsTextPainterlayout cost duringbuildwhen both explicitwidthandheightare provided. - Fix:
LiquidGlassNavigationBarnow correctly syncs changes toleadingItems,trailingItems, andtitleTextStyleto the native bar after initial creation; a matchingsetItemschannel method was added on the Swift side.
0.0.1 #
- Convert package template to Flutter iOS plugin scaffolding.
- Add native Liquid Glass-style tab bar behavior for iOS.
- Add runtime fallback rendering for non-iOS platforms.
- Expose
LiquidGlassTabBarFlutter widget API. - Add interactive example app showcasing the tab bar.
- Remove standalone
LiquidGlassViewitem from the package API.