cupertino_native_better 1.6.0
cupertino_native_better: ^1.6.0 copied to clipboard
Native iOS Liquid Glass widgets for Flutter with pixel-perfect fidelity. Includes Button, Icon, TabBar, Slider, Switch, and more with reliable version detection.
1.6.0 #
Minor rather than patch because
CNGlassEffectgains a value (clear). That is source-breaking for anyone with an exhaustiveswitchover the enum. 1.5.5 was tagged during development and never published; everything it contained is here.
Added — #69 CNGlassEffect.clear, and LiquidGlassConfig.effect is now honoured #
LiquidGlassConfig.effect was serialised over the channel as 'effect': config.effect.name, parsed natively, stored, handed to the SwiftUI view and even used as an .animation(_:value:) key — and then glassEffectForConfig() ignored it and hardcoded Glass.regular on both iOS and macOS. CNGlassEffect.regular and CNGlassEffect.prominent therefore rendered identically, and SwiftUI's third variant, Glass.clear, was unreachable.
clear matters for glass over imagery: regular frosts what is behind it, clear lets it through — the treatment Apple's own media controls use over video and artwork.
Fix: CNGlassEffect.clear is added and both containers honour the value they were already being given.
prominent is unchanged and still falls back to regular — SwiftUI's Glass has no prominent counterpart to map onto. The enum value is kept so existing code compiles and so the mapping can be filled in later; it is now documented as a fallback rather than left to be discovered by screenshot. Note also that clear barely blurs, so content layered on top of it needs its own contrast handling (a shadow or scrim) in a way regular does not.
Reported and fixed by @johndavid92 (PR #69). Verified on an iPhone 16 Pro / iOS 26 simulator with panels of identical size and position over vertical stripes that never vary along y — so every panel sees a pixel-identical backdrop and any difference is the glass alone. RMSE between panel interiors:
| comparison | RMSE |
|---|---|
before — regular vs prominent |
0.0010 |
| noise floor (same panel shifted 1px) | 0.0027 |
after — regular vs prominent |
0.0010 (unchanged) |
after — regular vs clear |
0.337 |
Before the fix the two effects differed by less than a one-pixel shift of the same image: peak difference 1/255, zero differing pixels at 2% fuzz. macOS could not be built in the verification environment (pre-existing Swift Package Manager scaffolding issue, unrelated to the change), so the macOS glassEffectForConfig body was type-checked against the MacOSX26.0 SDK directly instead.
CNGlassButtonGroup still hardcodes Glass.regular in GlassButtonSwiftUI.swift on both platforms — a separate surface that does not take CNGlassEffect, left for a follow-up.
Fixed — #66 Uncoloured imageAsset icons don't adapt to Liquid Glass #
An imageAsset supplied without an explicit color was handed to UIKit as a non-template UIImage, so it kept its baked pixels. On an iOS 26 glass button the material resolves its light/dark appearance from the backdrop, so a black-baked glyph over a dark backdrop went invisible — and a white-baked one did the same over a light backdrop. customIcon and SF Symbols already receive .alwaysTemplate and were unaffected, which is what made the inconsistency easy to miss until an app mixed the two.
Fix: uncoloured image assets are rendered as templates in CupertinoButtonPlatformView and CupertinoPopupMenuButtonPlatformView, so the control's tint — and Liquid Glass's automatic foreground adaptation — applies. Explicit imageAsset.color values keep their existing baked-tint behaviour.
Templating is gated on the artwork actually being single-colour. .alwaysTemplate keeps only the alpha channel: lossless for a black glyph or a grey stroked SVG, but it flattens anything multi-colour to a silhouette. ImageUtils.isEffectivelyMonochrome(_:) samples the image on a capped 32×32 grid, compares only near-opaque pixels (so antialiased edges don't read as a second colour) and returns on the first distinct colour — so multi-colour assets are left exactly as they were.
Also in this change:
GlassButtonSwiftUI.effectiveIconColorwastint ?? iconColor, so a control-level tint overrode an explicit per-icon colour. NowiconColor ?? tint.- The
setButtonIcontemplate pass is scoped to image assets. Applied to every icon source it would also template SF Symbols, so amulticolor/palettesymbol without an explicit colour rendered correctly on creation and then flattened after any icon update.
Behaviour change: the plain glass style no longer sends a forced theme-accent tint from Dart, so the native side can resolve its own foreground. On iOS 26 this is mostly invisible for icons — UIButton.Configuration.glass() already overrode the image foreground, so a glass SF Symbol rendered as the adaptive system colour rather than the accent even before this release. It is visible on glass button labels, which now use the system label colour instead of CupertinoThemeData.primaryColor. Set CNButton.tint explicitly to keep an accent-coloured glass label. prominentGlass keeps its accent fallback, and the pre-iOS-26 fallback still reads primaryColor from the Cupertino theme.
Reported and fixed by @azharul-islam (PR #66). Reproduced and verified on an iPhone 16 Pro / iOS 26 simulator: over a black backdrop in light mode, a black-baked imageAsset was invisible on the glass before the fix and adapts to white after, while a deliberately four-colour asset keeps all four colours.
Fixed — #67 customIcon glyphs pixelated on thin strokes #
iconDataToImageBytes painted the glyph at 1× fontSize, then the "scale to fill" re-blit upscaled the cropped ink — icon fonts pad their glyphs inside the em box, so the ink is smaller than the requested size. At 1× there is no sub-pixel detail to upscale from, so thin strokes (chevrons, checkmarks, plus signs, outline glyphs) came out with visible staircase edges. Every customIcon path in the package routes through this function: CNButton, CNPopupMenuButton, CNTabBar, CNIcon, CNGlassButtonGroup.
Fix: the glyph is rasterised onto a canvas scaled to 2× the device pixel ratio, and the re-blit downscales with FilterQuality.high instead of the Paint() default of FilterQuality.none. Output dimensions are unchanged.
Measured on an iPhone 16 Pro / iOS 26 simulator against a ground-truth render — mean absolute alpha error, chevron at 20 pt, 3× screen:
| intermediate buffer | MAE | |
|---|---|---|
| before | 27k px | 7.00 |
FilterQuality change alone |
27k px | 6.82 |
2× canvas + FilterQuality.high |
108k px | 2.98 |
| 4× linear (font and canvas) | 434k px | 1.78 |
The FilterQuality change on its own is close to a no-op — the re-blit is only a ~1.1× upscale, so the filter barely matters and it measured worse on 5 of 12 glyph/size combinations. The intermediate resolution was the actual problem. The last row is what PR #67 originally proposed; it measures better but is not distinguishable at real icon sizes, and it costs 16× the pixels in a buffer that is walked pixel-by-pixel to find the ink bounds.
Reported and fixed by @azharul-islam (PR #67), refined to 2× on merge.
Performance — iconDataToImageBytes is now memoised #
There was no cache on icon rasterisation, and CNButton and CNIcon build FutureBuilder(future: iconDataToImageBytes(...)) inside build() — so every rebuild re-ran three canvas passes plus a pixel-by-pixel scan of the intermediate buffer.
Results are memoised per (glyph, font, size, colour, device pixel ratio). Futures rather than results are cached, so concurrent callers share one rasterisation; and because FutureBuilder only re-subscribes when its future's identity changes, those widgets now keep their snapshot across rebuilds instead of dropping back to a blank placeholder. The map is capped at 256 entries with oldest-first eviction, since size is caller-supplied and can be animated. clearIconImageCache() is exported for tests and for apps that swap icon fonts at runtime.
Example app #
- New:
Testing → PR #66: glass imageAsset tint— puts the same artwork through SF Symbol,customIcon, uncolouredimageAssetand explicitly colouredimageAsseton a glass button, over a selectable backdrop (black / white / photo / split) and either brightness. Includes a deliberately four-colour row as the regression check for template flattening, plus popup-menu and button-group rows. - New:
Testing → PR #67: icon supersampling— renders the same glyph through the renderer with the supersample factor andFilterQualityexposed as separate knobs, blown up nearest-neighbour so real pixels are visible, with mean-alpha-error and intermediate-buffer cost per variant. - New:
Testing → PR #69: LiquidGlass config.effect— one panel perCNGlassEffect.valuesover vertical stripes that never vary along y, so every panel sees a pixel-identical backdrop and two panels that render differently can only differ because of the glass. Backdrop switches to a photo for the over-imagery case. - New:
example/lib/pr_probe_entry.dart— an alternate entrypoint that boots straight into one test page in one configuration via--dart-define, for capturing before/after screenshots without tapping through the demo list.
1.5.4 #
Fixed — #64 LiquidGlassContainer glass renders short in the bottom safe area #
When a LiquidGlassContainer's frame reached into the bottom safe area — the common "floating bottom panel" layout on home-indicator devices — the native glass stopped short of the frame it was given. Flutter-drawn children stayed in the right place, so they visually spilled outside the glass.
Cause: LiquidGlassContainerPlatformView hosts the SwiftUI glass in a UIHostingController, which propagates the screen's safe area into its SwiftUI content. Where the platform view's frame overlapped the home-indicator inset, the hosted GeometryReader/shape was inset by that overlap, so the glass filled only the reduced size.
Fix: self.hostingController.safeAreaRegions = [], matching the intent of the existing safeAreaInsets override in CupertinoSwitchPlatformView.swift. safeAreaRegions is iOS 16.4+ and the class is already @available(iOS 26.0, *), so no extra availability guard is needed.
Reported and fixed by @fbernack (PR #64), who measured up to 22 pt short on an iPhone 17 Pro (iOS 26.4 simulator). Independently reproduced and verified on a physical iPhone running iOS 26.5: with a 140 pt panel pinned flush to the bottom of a 34 pt inset, the glass fell ~21 pt short of its frame before the fix and filled it completely after.
macOS is unaffected — LiquidGlassContainerNSView uses NSHostingController, and macOS windows have no bottom safe-area inset in practice.
Example app #
- New:
Testing → PR #64: LiquidGlass safe-area clip— a glass panel whose bottom offset, height, and shape (rect/capsule/circle) are adjustable live. A solid red strip fills exactly the safe-area inset (the zone the bug leaves uncovered), a Flutter-drawn magenta outline marks the panel's true frame, and a cyan dashed line marks the safe-area boundary — so "glass short of frame" is directly visible rather than a judgement call. Sliding the bottom offset past the boundary shows whether the glass stays attached to its frame.
1.5.3 #
Fixed — #62 CNTabBar taps swallowed by ancestor gesture recognizers #
CNTabBar stopped responding to taps whenever any ancestor widget held an active gesture recognizer over the same region. The reporter's minimal repro:
GestureDetector(
onLongPress: () {}, // any active recognizer is enough
child: CNTabBar(items: [...], currentIndex: i, onTap: ...),
)
Tapping a tab did nothing. In the wild this surfaced via requests_inspector, which wraps the whole app in a long-press GestureDetector — installing it silently killed the tab bar.
Cause: _buildNativeTabBarPlatformView constructed the iOS UiKitView / macOS AppKitView without a gestureRecognizers set. With none supplied, the platform view is a passive arena participant and only receives touches if nothing else claims them, so the ancestor won uncontested.
Fix: both platform-view branches now pass Factory<EagerGestureRecognizer>(() => EagerGestureRecognizer()). EagerGestureRecognizer.addAllowedPointer immediately calls resolve(GestureDisposition.accepted), so the platform view claims the pointer on pointer-down and touches forward to the native UITabBar regardless of what is competing.
Note the trade-off: gestures made directly over the tab bar (long-press, drag) now go to the native bar instead of bubbling to ancestor recognizers. This is the intended behaviour for a tab bar, which is only ever tapped.
Reported and originally patched by @MohamedAboElM3aTy (#62, PR #63). The submitted patch used a bare TapGestureRecognizer(), which is a no-op — Flutter's TapGestureRecognizer.isPointerAllowed returns false when none of onTapDown/onTap/onTapUp/onTapCancel/onTapMove are set, so the recognizer never enters the arena. Amended to EagerGestureRecognizer before merge and verified on-device (iPhone, iOS 26.5).
Known — same gap remains in other components (#65) #
The identical issue affects other platform-view widgets and is tracked in #65. It is not a uniform fix, so it was deliberately kept out of this release:
button.dart,switch.dart,slider.dart,popup_menu_button.dartpass a bareTapGestureRecognizer()and therefore carry the same latent no-op.segmented_control.dart,glass_button_group.dart,icon.dart,liquid_glass_container.dart,floating_island.dart,search_bar.dart,search_scaffold.dartpass nogestureRecognizersat all.- Several of those must not get a claiming recognizer —
icon.dartis decorative (it would steal taps from parent widgets),liquid_glass_container.dart/floating_island.dart/search_bar.dartdeliberately wrap the platform view inIgnorePointerand handle interaction Flutter-side, andsearch_scaffold.dart's view isPositioned.fillbehind all Flutter content. - The button-like components need an armed tap recognizer (
TapGestureRecognizer()..onTap = () {}) rather than an eager one, so drags still fall through to a parent scrollable —button.dart:654documents that intent explicitly.
Example app #
- New:
Testing → #62 / PR #63: CNTabBar tap gesture arena— three switchable scenarios verifying the fix on-device: an ancestorGestureDetector(onLongPress:)(the reporter's minimal repro), an ancestorPageView(drag-vs-tap competition), and a no-ancestor baseline for regression coverage. Each scenario avoids nesting aScrollablein the gesture region so ancestor gestures resolve deterministically.
1.5.2 #
Fixed — #61 CNBottomSheet.showCupertino compile error on Flutter < 3.44 #
CNBottomSheet.showCupertino in v1.5.1 called Flutter's showCupertinoSheet() with a scrollableBuilder: named parameter. That parameter was only added to showCupertinoSheet in Flutter 3.44.0 (via flutter/flutter#177337, 2026-01-23). Anyone consuming the package on stable 3.35 – 3.41.x — including a substantial share of production Flutter users, FlutterFlow environments, and CI setups — hit a hard compile error:
No named parameter with the name 'scrollableBuilder'.
Fix: switched to builder: (the current API on 3.35 – 3.41; deprecated-but-still-accepted on 3.44+). The lambda didn't use its ScrollController anyway, so the swap is behaviour-preserving. Compatible from Flutter 3.35 through today's master.
Thanks @ArkayGit for the clean, precisely-scoped bug report.
Fixed — #46 CNToast queue deadlock after route pop #
CNToast stored the caller's BuildContext on each queued _ToastEntry and re-resolved Overlay.of(entry.context) inside _showNext() — which fires from a Timer after each toast's duration. If the caller's widget was disposed while toasts were pending (typical: user shows a toast then taps back), the framework crashed with:
Looking up a deactivated widget's ancestor is unsafe.
Worse, after the throw, _isShowing was left true and the queue was permanently stuck — every subsequent CNToast.show/success/error/warning/info call would silently _queue.add() without displaying, until app restart. Only CNToast.loading() still worked (it bypasses the queue and inserts into the overlay directly).
Fix (in lib/components/toast.dart):
_ToastEntrynow stores anOverlayState(resolved eagerly atCNToast.show()time), not aBuildContext._showNext()no longer touches the caller's context and cannot throw on a deactivated widget.- Added
entry.overlay.mountedshort-circuit and atry/catcharoundoverlay.insert(...)— if the OverlayState disappears between enqueue and display (extremely rare), the entry is dropped and the queue keeps draining._isShowingis guaranteed to reset.
Fixed — #46 CNToast renders with yellow double-underline under MaterialApp (reporter's screenshot) #
The same issue also carried a second, unrelated defect surfaced by @macedondev's screenshot: text rendered as dim red monospace with a yellow double-underline. That's MaterialApp._errorTextStyle (flutter/lib/src/material/app.dart:45), Flutter's built-in "your Text has no Material ancestor" debug fallback. MaterialApp installs it as the ambient DefaultTextStyle at the app root; _ToastOverlay renders its Text inside an OverlayEntry with no Material between them, so the fallback style wins. CupertinoApp doesn't install _errorTextStyle, which is why the maintainer couldn't reproduce the bug locally.
Fix: wrap the toast content in Material(type: MaterialType.transparency, ...) inside _ToastOverlay.build(), positioned between IgnorePointer and Align so Positioned still sees the Overlay's Stack as its direct parent (a Material between them would break Positioned's parent-data contract and throw Incorrect use of ParentDataWidget). Transparent Material paints nothing, so CupertinoApp users see no visual change; MaterialApp users get proper text-style resolution and the yellow underlines are gone. Applies to both the queued (_showNext()) and direct-insert (loading()) toast paths.
Thanks @macedondev for the report.
Example app #
- New:
Testing → #46: CNToast use_build_context_synchronously— two labeled scenarios. Bug A (queue crash on dispose): spam-and-pop button that queues 5 toasts then pops the route ~250ms in, reproducing the "Looking up a deactivated widget's ancestor" crash pre-fix and demonstrating the queue-drains-cleanly behaviour post-fix. Bug B (yellow underlines): a route whose body is a nestedMaterialAppsoCNToastfires into an overlay with the ambient_errorTextStyle— reproduces the reporter's exact rendering. Plus a quick-trigger row for all sixCNToast.*variants for regression coverage.
1.5.1 #
Fixed — #53 PlatformView z-order bleed under bottom sheets #
iOS hybrid composition was reusing the same PlatformViewContainer for both a host-page CN-widget and a CN-widget inside a presented sheet, causing the host-page widget's pixels to leak through the sheet's scrim (and vice-versa).
Fix: new ModalHideMixin (in lib/utils/modal_hide_mixin.dart) applied to all 9 CN widgets that use a PlatformView — CNButton, CNGlassButtonGroup, CNSwitch, CNSegmentedControl, CNPopupMenuButton, CNSearchBar, CNLiquidGlassContainer, CNFloatingIsland, CNSlider. Each widget now destroys its PlatformView (with a same-size placeholder reserving the layout slot) while a sheet covers it, and recreates it when the sheet dismisses.
Each affected widget gained an autoHideOnModal: bool = true constructor parameter so users can opt out per-instance.
New — CNBottomSheet + CNSheetGeometryProbe #
For the modal-hide to be position-aware (only widgets actually behind the sheet hide, not the entire host route), the sheet has to publish its rect each frame. Two new public APIs cover this:
-
CNBottomSheet(inlib/components/bottom_sheet.dart) — drop-in wrappers that inject the probe automatically:CNBottomSheet.show(context: context, builder: (ctx) => MySheet()); CNBottomSheet.showCupertino(context: context, builder: (ctx) => MySheet()); CNBottomSheet.showModalPopup(context: context, builder: (ctx) => MySheet()); -
CNSheetGeometryProbe— wrap your own sheet builder manually if you need to keep using the framework APIs directly:showModalBottomSheet( context: context, builder: (ctx) => CNSheetGeometryProbe(child: MySheet()), );
Without one of these the package falls back to a conservative "hide every CN-widget on this route while any modal is up" behavior — safe, but coarser than needed (an app-bar CN-button could disappear behind a 30%-height sheet).
CNTabBarRouteObserver also gained topModalRect: ValueNotifier<Rect?> and publishTopModalRect(Rect?), used by the probe.
Fixed — #55 CNPopupMenuItem.isDestructive #
CNPopupMenuItem gained isDestructive: bool = false. When true:
- iOS 14+ adds
UIMenuElement.Attributes.destructive→ the label renders in the system destructive red (previously only the icon could be red viaiconColor). - iOS 13 legacy fallback uses
UIAlertAction.Style.destructive. - iOS < 26 / non-iOS Cupertino fallback uses
CupertinoActionSheetAction(isDestructiveAction: true).
Thanks @ashellz for the report.
Fixed — CNGlassButtonGroup remount blink #
CNGlassButtonGroup's FutureBuilder returned SizedBox.shrink() for one frame between the modal-hide placeholder removal and the platform view actually mounting — text below jumped up then back down on every sheet dismiss. The pending branch now mirrors the placeholder's axis-aware dimensions so the layout slot is held across the swap.
Fixed — PR #57 macOS CNSwitch rendered as a checkbox #
The macOS Toggle defaulted to a checkbox under recent SDKs. Applied .toggleStyle(.switch) to force the switch appearance. iOS unaffected. Thanks @jonathanfristedt.
Fixed — MissingPluginException storm during transitions #
ch.invokeMethod('setTransitioning', …) calls now use .catchError((_) {}) to swallow async rejections, and fallback platform-view classes (iOS < 26) register no-op MethodChannel handlers so Dart-side calls from ModalHideMixin and route-transition containment don't throw.
Docs #
pubspec.yamldocumentation:now points to https://gunumdogdu.com/docs.- README documents
CNBottomSheet/CNSheetGeometryProbeusage and the navigatorObserver requirement for modal-hide to work.
Example app #
- New:
Testing → #53: CNButton under bottom sheet— four sheet-opener variants over a host page full of CN widgets. - New:
Testing → #55: PopupMenu isDestructive— text, icon, and mixed menus with destructive items. - New:
Testing → Glass widgets modal halo test— all 9 CN widgets behind sheets. - New:
Testing → CNButton modal halo test— modal route push/pop animations. - New:
Testing → #37: CNAppBar button halo test.
Known limitations #
CNGlassButtonGroupglass merging at default spacing still shows a "dumbbell" between buttons. The single-uniform-pill rewrite hit a SwiftUI hit-testing limitation (.glassEffect()intercepts touches at a layer below.allowsHitTesting(false)); a UIKitUIVisualEffectViewrewrite is queued for the next release.
1.5.0 #
New — CNTabBarNative gains minimize, native lists, accessory & root mode #
Building on the existing CNTabBarNative (the native iOS 26 Liquid Glass tab bar), this release adds:
- Minimize-on-scroll (resolves #32) —
minimizeBehavior:withCNTabMinimizeBehavior.{automatic, never, onScrollDown, onScrollUp}, changeable at runtime viasetMinimizeBehavior(...). Requires a tab backed by aCNNativeList, since iOS drives the minimize from a real native scroll view. - Native list tabs —
CNTab(nativeList: CNNativeList(items: [CNListItem(...)]))renders a native scrollable list;onListItemTap(tabIndex, itemIndex)reports taps. NewCNNativeList/CNListItemmodels (now exported from the package). - Bottom accessory pill —
bottomAccessory: CNTabAccessory(...)floats above the bar and slides inline when it minimizes;onAccessoryTapreports taps; show/update/hide at runtime withsetBottomAccessory(...)(passnullto hide). - Presentation modes —
asRoot:selects modal presentation (default) or root presentation; in root mode a tab with nonativeListhosts your real Flutter UI. - Search filtering option —
nativeSearchFilter(defaulttrue) filters the search tab's own list locally; setfalseto drive results yourself viaonSearchChanged+setItems. - Mutation API —
setItems(...)for dynamic/paginated data, plusonDismissed(fires when the bar is closed natively, e.g. via the ✕ button).
The native manager was rewritten (CNNativeTabBar.swift replaces CNNativeTabBarManager.swift) on a stable SwiftUI TabView view tree.
Docs #
- Rewrote the README "Native iOS 26 Tab Bar (CNTabBarNative)" section: native-takeover mental model, a
CNTabBarvsCNTabBarNativecomparison table, presentation modes, and a per-method API reference. Added a preview GIF (minimize + accessory + search). - Documented that
CNTabBarNativeis a native takeover, not a Flutter bottom-nav — for Flutter screens per tab, useCNTabBar(clarifies #7).
Deprecated #
CNTabBarNative.enableparametersonSearchSubmitted,onSearchCancelled,onSearchActiveChanged— the search tab now reports throughonSearchChanged; these are no longer fired and will be removed in a future major release.CNTabBarNative.checkIsEnabled()— use the synchronousisEnabledgetter instead.
Notes #
- #48 (color the unselected tab item): investigated and intentionally not added to
CNTabBar. iOS 26's Liquid Glass tab bar enforces the system color for unselected items and ignores customization — verified on-device with both the legacyUITabBar.unselectedItemTintColorand the modernUITabBarAppearanceper-stateiconColor/ titleforegroundColor. The selectedtintis honored; the unselected state is system-owned, so a public knob would silently no-op on the package's target OS.
1.4.6 #
Bug Fixes #
- Fixed #39 / merged PR #42 —
CNTabBar.iconSizeis now honored forcustomIconitems (anyIconDatalikeCupertinoIcons.houseorIcons.home). Previously the custom-icon rasterizer was hardcoded to 25pt regardless of the bar-leveliconSizeor per-itemicon.size. Bug originally surfaced via #39 (SVGimageAssetlayout glitch on v1.4.3) and addressed by @Azzeccagarbugli's PR #42, with an extra refinement on top:lib/components/tab_bar.dart:_prepareCreationParamsnow passeswidget.iconSize ?? item.icon?.size ?? 25.0toiconDataToImageBytes; same precedence onactiveCustomIcon;_buildTabIcon(Flutter fallback) mirrors the precedence across all icon kinds.lib/utils/icon_renderer.dart: full rewrite oficonDataToImageBytes. Drops the heavyRenderRepaintBoundary+BuildOwnersetup in favor of aTextPainter+ canvas + alpha-channel crop approach. Final step (refinement on top of the PR): re-blit the cropped glyph into a squaresize × sizecanvas with the glyph scaled to fill, so custom icons at a given pointSize match SF Symbol's visible ink at the same pointSize (matches SF Symbol's "pointSize is ink size" convention — previously, font em-box padding made customIcon visually smaller than equivalent SF Symbols at the same size).test/widget_test.dart: 6 new widget tests verifying theiconSize > icon.size > 25ptprecedence across SF Symbol / customIcon / imageAsset in the Flutter fallback path.
New #
-
Swift Package Manager support — fixes #44. The plugin now ships a
Package.swiftmanifest for both iOS and macOS targets and is recognized bypanaas "Swift PM-ready".Flutter's SPM rollout is well underway: SPM support landed in Flutter 3.24 (Aug 2024) as opt-in, became the default for new apps in Flutter 3.44, and CocoaPods is being phased out — Firebase stops publishing to CocoaPods in October 2026 and the CocoaPods registry becomes read-only on December 2, 2026. Plugins without SPM support already lose pana points and trigger build warnings on modern Flutter versions.
Compatibility (no users locked out): the plugin's own pubspec constraints stay at
sdk: ^3.9.0/flutter: '>=3.3.0'. The SPMPackage.swiftsits alongside the existing podspec and is only consulted by Flutter 3.24+. Older Flutter consumers continue to resolve the package via CocoaPods exactly as before — the podspec'ssource_filespath was updated to point at the new SPM-shaped source directory (ios/cupertino_native_better/Sources/cupertino_native_better/**, mirrored on macOS) so both build paths produce the same result.Consumer Flutter Build path 3.3 – 3.23 (pre-SPM) CocoaPods via podspec. Package.swift ignored. Unchanged from v1.4.5. 3.24 – 3.43 (SPM opt-in) CocoaPods by default; SPM if flutter config --enable-swift-package-manageris set. Both paths work.3.44+ (SPM default) SPM via Package.swift.Thanks to @josec-ecw for the PR.
Example app #
- Added:
Testing → PR #42: CNTabBar iconSize (customIcon)— three side-by-sideCNTabBars (SF Symbol / customIcon / SVG imageAsset) with aniconSizeslider so all three icon kinds can be verified to scale identically. Four Stratis UI Figma SVGs bundled (camera-01,card-add,chromecast,home-03) — same source as #39 reporter — for SVG verification.
Pana #
- 160/160 (now includes "Swift PM-ready" recognition)
1.4.5 #
Bug Fixes #
-
Fixed #35 / #41 (recreate animation on launch + navigation) —
CNTabBarno longer plays a visible "morph through every tab" animation on app launch or after navigating away and back.- Launch glitch (#35): Swift
refreshcyclesbar.selectedItemthrough every tab to force UITabBar's label layout (workaround for an old "5 items, sporadic missing labels" bug — Issue #6). On iOS 26 with Liquid Glass, that cycling was visible as the selection pill morphing through every tab. The cycle is now wrapped inUIView.setAnimationsEnabled(false) … true— labels still render correctly, but the pill no longer animates between items. Tab bar appears instantly with the configuredcurrentIndex. - Recreate-on-return (#41 part 1):
autoHideOnPageTransitionpreviously swapped the platform view to aSizedBoxduring route slides, destroying the nativeUITabBar. On return, a fresh view was created and_onCreatedre-ransetSelectedIndex+refresh→ visible animate-to-index. Fix: whenautoHideOnPageTransitionis on,CNTabBar.buildnow always returns anIndexedStack(children:[SizedBox, UiKitView]); only the painted index toggles between 0 and 1 across the transition. TheUiKitView's element stays mounted in both states, so the nativeUITabBaris preserved across navigation. Bar just appears with the correct index, no animation.
- Launch glitch (#35): Swift
-
Fixed #41 (PlatformViewGuard 500 ms fallback flash on first build) — non-iOS26 fallback widgets briefly visible during cold start.
- Cause:
PlatformViewGuard.ensureScheduledalways delayed platform-view creation by 500 ms to give Flutter'sFlutterPlatformViewsControllertime to purge stale registrations from a previous Dart isolate after a hot restart. That race only exists in debug; release builds (cold-start, no isolate recycling) were paying the same flash-of-fallback cost for nothing. - Fix:
PlatformViewGuardis now ready immediately inkReleaseMode. Native iOS 26 widgets render from the first frame in production; debug-mode hot-restart safety is preserved unchanged.
- Cause:
-
Fixed #36 follow-up —
LiquidGlassContainer's rectangular layer drop shadow leaking past its rounded glass corners while a modal/sheet was presented above (was already targeted in v1.4.4 but only clipped the rectangular layer bounds; rounded clip wasn't applied across all shape configurations correctly). The corner-radius clip inapplyTransitionContainmentnow consistently matchesrect(configured cornerRadius),capsule(min(width, height)/2), andcircle(min(width, height)/2) — no more square shadow nubs.
New #
-
Fixed #40 (CNButton label customization) —
CNButtonConfignow accepts:labelFontFamily— custom font family (must be registered inInfo.plistor as a Flutter font asset).labelFontSize— point size override.labelColor— explicit foreground color (overrides thetint-derived default for non-filled styles, and the system default for filled / borderedProminent / prominentGlass).labelFontWeight— FlutterFontWeightoverride.
Implementation: Swift side applies them via
UIButton.Configuration.titleTextAttributesTransformer, so the overrides take effect on the native label without losing the iOS 26 Liquid Glass / prominent / tinted button background. Both creation-time (creationParams) and runtime (setLabelStylechannel call from_syncPropsToNativeIfNeeded) updates are supported. FlutterFontWeight.value(0-8) is mapped toUIFont.Weight.Example:
CNButton( label: '1', tint: CupertinoColors.systemOrange, onPressed: () {}, config: const CNButtonConfig( style: CNButtonStyle.prominentGlass, width: 80, minHeight: 80, labelFontSize: 36, labelFontWeight: FontWeight.w600, labelColor: CupertinoColors.white, ), )
Behavioural details #
CNTabBar.autoHideOnPageTransitionkeeps its defaulttrue. With the IndexedStack-based hide it's now zero-cost: state is preserved across navigation while still preventing the original page-wide PlatformViewLayer occlusion artifact during route slides.- Multi-label
CNButton(e.g. dial-pad style: large number + small letters underneath) is intentionally not added toCNButton's API. The same effect composes cleanly viaLiquidGlassContainerwrapping aColumnof two FlutterTexts — fullTextStylefreedom on both labels with the native iOS 26 Liquid Glass background. See the closing comment on Issue #40 for a snippet.
Example app #
- Added:
Testing → #40: CNButton label style— interactive screen for verifying the new label-style params (font-size slider, color swatches, font-family segmented control, style picker).
Pana #
- 160/160
1.4.4 #
Bug Fixes #
- Fixed #36 —
LiquidGlassContainer's rectangular layer drop shadow leaking past its rounded glass corners while a modal/sheet was presented above. Visible as four square shadow nubs at the card's corners through the modal's scrim, even though the visible glass was rounded.- Root cause:
applyTransitionContainment(true)(added in v1.4.3 for the dynamic halo containment) only setclipsToBounds = trueon the container's CALayer, which clips to the rectangular layer bounds — leaving the four corners outside the rounded glass shape unclipped. The layer's drop shadow rendered into those corners and bled through the modal scrim. - Fix: in
LiquidGlassContainerView.swift,applyTransitionContainment(true)now also setscontainer.layer.cornerRadius(and the hosting view's) to match the configured glass shape:rectshape → uses the configuredcornerRadiuscapsule/circle→min(width, height) / 2
- The clip is now rounded, matching the visible glass exactly. Reverted to 0 when containment goes inactive so it doesn't affect the at-rest visual.
- Root cause:
Documentation #
- README: added a prominent "Required Setup: register
CNTabBarRouteObserver" section directly under Quick Start, documenting:- Why the observer is needed (hybrid composition, halo containment, z-order with sheets).
- Where to register it:
CupertinoApp/MaterialApp/GoRoutersnippets. - What it fixes across all 7 glass widgets, with explicit references to Issues #29, #31, #36.
- Manual
markAnyModalActive/markAnyModalInactiveAPI for non-route overlays (Scaffold.showBottomSheet). - Several users reported needing this observer to fix sheet bleed-through; with this section it should now be impossible to miss during initial setup.
Example app #
- Added:
Testing → #36: LiquidGlassContainer behind modal— focused reproduction page for Issue #36. Card-shapedLiquidGlassContainermatching the issue reporter's_AdaptiveGlassContainerwidget exactly (cornerRadius: 15,rect,EdgeInsets.all(13), no tint), four sheet variants including the reporter's exactshowModalBottomSheetinvocation (rounded top, scaffold-bg,Clip.antiAlias, bounce animation,useRootNavigator: true), and aCalendarDatePickerinside each sheet matching the modal content from the issue's screenshots. White scaffold + black/white pill buttons mirror the reporter's app styling.
Pana #
- 160/160
1.4.3 #
Bug Fixes #
-
Fixed #34 — CNButton glass capsule not stretching with its parent frame; icon overflowing a small pill when wrapped in
SizedBox/Expanded. Regression introduced in v1.4.0.- Root cause: the v1.4.0 "feat: fixes" commit added always-on
container.clipsToBounds = true+uiButton.clipsToBounds = true(plus layer shadow/background clearing) across every iOS 26 glass widget as part of the #29 halo containment.UIButton.Configuration.glass()renders its capsule via an internal background subview whose visual size includes a soft-edge glow extending slightly beyond the button's layer bounds — the always-on clipping was cropping that glow AND preventing the capsule from growing with a stretched frame. - Fix: reverted all the always-on clipping across
CNButton,CNPopupMenuButton,CNFloatingIsland,CNGlassButtonGroup,LiquidGlassContainer,CNSearchBar. Containers are unclipped at rest, so the glass capsule renders its full soft-edge glow and stretches properly withSizedBox/Expanded/Container(width: ...).
- Root cause: the v1.4.0 "feat: fixes" commit added always-on
-
Fixed #29 (fully) — the original halo-during-route-transition artifact is now resolved via a dynamic containment pattern instead of always-on clipping. This also catches the popup/sheet bleed cases that the v1.4.0 fix didn't cover (popup routes, persistent bottom sheets).
- New native method
setTransitioning(active:)onCNButton,CNPopupMenuButton,CNFloatingIsland,CNGlassButtonGroup,LiquidGlassContainer,CNSearchBar, and the regularCNTabBarvariant. When active, it applies the halo-containment clipping + shadow/background clearing; when inactive, it reverts. - Dart side listens to two signals and calls
setTransitioning(true)when either fires:ModalRoute.secondaryAnimation— catchesCupertinoPageRoute/MaterialPageRouteforward/reverse transitions.- A new
CNTabBarRouteObserver.anyModalDepthcounter that tracks anyPopupRoute/ Sheet / Popup / Dialog-named route (showCupertinoSheet,showCupertinoModalPopup,showModalBottomSheet,DialogRoute, etc.).
- For the split-search variant of
CNTabBar(whose native container is intentionally unclipped so the floating search orb can render above the bar), the auto-hide trigger is broadened toanyModalDepthso popups over a search-enabled tab bar no longer leak shadow through the sheet's top edge.
- New native method
New #
-
CNTabBarRouteObserver.anyModalDepth(read-onlyValueListenable<int>) — broader counter than the existingmodalDepth. Tracks every modal-like route (allPopupRoutes plus any route whose runtime type containsSheet/Popup/Dialog). Used internally by the glass widgets for halo-containment activation. -
CNTabBarRouteObserver.markAnyModalActive()/markAnyModalInactive()— public manual API for non-route overlays thatNavigatorObservercan't see, notablyScaffold.showBottomSheet(persistent bottom sheet anchored toScaffoldState, not the Navigator):final controller = Scaffold.of(context).showBottomSheet(...); CNTabBarRouteObserver.markAnyModalActive(); controller.closed.whenComplete(CNTabBarRouteObserver.markAnyModalInactive);
Example app #
- Added:
Testing → CNButton modal halo test— stretched CNButton variants + 4 sheet types (showCupertinoSheet,showCupertinoModalPopup,showModalBottomSheet,showBottomSheet) for verifying halo containment across every overlay variant. - Added:
Testing → Glass widgets modal halo test— same 4-sheet matrix againstCNPopupMenuButton,CNGlassButtonGroup,LiquidGlassContainer,CNSearchBar,CNFloatingIsland. - Updated:
Stack+Positioned tab barandSplit-search clip reproscreens now include all 4 sheet types for regression coverage. - Added:
DefaultMaterialLocalizations.delegateto the rootCupertinoAppso demos can mixshowModalBottomSheet(Material) with Cupertino routes without addingflutter_localizations.
Behavioural details #
- At rest, glass widgets render the full iOS 26 Liquid Glass capsule (including the soft-edge glow that extends slightly beyond the view's layer bounds) and stretch to fill bounded parent frames. This matches native iOS behaviour.
- During a route transition or while a modal/sheet/popup/dialog is above the widget's route, the native container is clipped and layer shadows are suppressed — the visible change is essentially invisible (the widget's visible frame is unchanged), but Flutter snapshots of the outgoing/incoming page no longer include a halo that extends past the platform-view bounds.
CNTabBarwithoutsearchItemstill uses the narrow Sheet-onlymodalDepthheuristic for its auto-hide (avoids the recreate-and-restore flash on quick action-sheet popups).CNTabBarwithsearchItemuses the broaderanyModalDepthbecause its container can't be clipped (the floating search orb needs to render above the bar's top edge).
Pana #
- 160/160
1.4.2 #
New #
-
CNTabBarRouteObserver— aNavigatorObserverthat letsCNTabBarauto-hide while a full-screen sheet is presented over its route. Resolves Issue #31 (MaterialTextFieldinvisible insideshowCupertinoSheetwhenCNTabBaris in the bottom nav slot).The native
UITabBaris rendered inside a FlutterUiKitView. When a Flutter-rendered sheet route is presented over the same navigator, hybrid composition can leave the tab bar's UIView at a higher z-index than the modal's Flutter content — makingTextFields inside the sheet invisible and letting the bar bleed through during sheet drags. Auto-hide swaps the platform view for an empty placeholder while the sheet is up, mirroring what iOS does natively when aUITabBarControllerpresents a full-screen modal.Setup (one line per app):
CupertinoApp( navigatorObservers: [CNTabBarRouteObserver()], // ... )Or
MaterialApp(navigatorObservers: [CNTabBarRouteObserver()], ...). Without this observer registered,CNTabBarstill renders correctly — it just won't auto-hide on top of sheets and you may hit the Issue #31 z-order glitch. -
CNTabBar(autoHideOnModal: bool = true)— opt-out for the auto-hide behaviour. Defaulttrue. Setfalseto keep the tab bar visible behind sheets (rare; typically requires a native sheet that won't trigger the z-order issue).
Bug Fixes #
- Fixed: tab bar's selected index resetting to 0 after a modal/sheet closed and the platform view was recreated (Issue #2 page repro).
- Root cause: in
_onCreatedwe calledrefreshbeforesetSelectedIndex. The nativerefreshmethod (a workaround for the 5-item-label-rendering bug Issue #6) capturesbar.selectedItemat start, cycles through items asynchronously, then "restores" the captured value — overriding thesetSelectedIndex(currentIndex)we sent right after, leaving the bar stuck at the stalecreationParams.selectedIndex = 0. - Fix: swapped the order —
setSelectedIndexnow runs BEFORErefresh. Refresh then captures the correct index and restores to it. Applied to both the 50ms and 200ms recreation passes.
- Root cause: in
Behavioural details #
- Auto-hide is intentionally narrow: it triggers only for routes whose runtime type name contains
Sheet(CupertinoSheetRoute,ModalBottomSheetRoute). Action-sheet popups (CupertinoModalPopupRoute), dialogs, and regular page pushes do NOT trigger auto-hide. This avoids a visible "platform view recreate + index restore" jump animation on quick popups, while still fixing the z-order issue for full-screen sheets.
Example app #
- Added:
Testing → #31: TextField — NO search variant (hypothesis test)— same flow as the Issue #31 reproduction but withCNTabBarconfigured withoutsearchItemandautoHideOnModal: false, used to verify the bug isn't search-specific. - Added: registered
CNTabBarRouteObserver()on the example app'sCupertinoAppso all demo screens benefit from auto-hide.
Pana #
- 160/160
1.4.1 #
Bug Fixes #
- Fixed:
CNTabBariOS 26 Liquid Glass selection pill was being cropped at its top edge after the v1.4.0 Issue #2 fix.- Root cause: v1.4.0 clipped the platform-view container to block the Liquid Glass drop shadow from bleeding over modal bottom sheets. The same clip also cut off the selection pill, which extends ~12–14pt above the bar's top during its morphing animation between tabs.
- Resolution: the container still clips (shadow containment preserved), but the UITabBar is now positioned 14pt below the container's top edge, and the reported intrinsic height is bar-height + 14pt. The Liquid Glass selection pill — including its morph animation when rapidly switching tabs — renders fully inside the clipped container. Bar's visible position is unchanged at the bottom of the allocated space.
- Applied to all 5 layout sites: single-bar init, split-bar init, and the equivalent setLayout rebuilds, for both iOS 26+ and iOS < 26 code paths.
Example app #
- Added:
Testing → #33: SVG in CNTabBar— reproduction screen mirroring the SVG-icons-in-CNTabBar pattern reported in Issue #33, including the reporter'sNavBarItemwrapper,iconSize: 24, andtintconfiguration. Local iOS 26 verification shows SVGs render correctly; the screen is published so the reporter (and future users hitting the same symptom) can confirm on their own setup. - Added:
Testing → Stack+Positioned tab bar— demonstrates theStack+Positioned(bottom: 0)layout pattern as an alternative toScaffold.bottomNavigationBarfor users who need custom z-order control. - Enhanced:
Testing → CNTabBar split-search clip— bright teal background and a modal-bottom-sheet trigger button so both the search-orb top-edge clip and the Issue #2 shadow-bleed scenarios can be verified side-by-side on one screen. - Bumped: example app iOS deployment target from 14.0 to 15.0 (required by the plugin's
s.platform = :ios, '15.0').
Known issues #
- iOS simulator (not real device): iOS 26 Liquid Glass rendering on the simulator is software-rasterized and has visible differences from real Metal hardware. You may see the Liquid Glass selection pill appear slightly clipped at the top, or a brief rectangular outline around buttons on press. These artifacts do not appear on real iOS 26 devices. Always verify Liquid Glass behavior on a real iPhone/iPad before treating a visual quirk as a package bug.
Pana #
- 160/160
1.4.0 #
Bug Fixes #
-
Fixed:
CNTabBartop-edge shadow bleeding over modals / bottom sheets — the regression of Issue #2 that landed between v1.3.0 and v1.3.8 (Issue #2)- Root cause: in v1.3.3 the
clipsToBounds = truecontainment from the original v1.3.0 fix was made conditional and disabled on iOS 26+. That removed the only thing keeping the UITabBar's top-edge hairline inside the platform view's bounds. container.clipsToBounds = truerestored unconditionally on the regular tab-bar platform view (5 sites — single-bar and split-bar in bothinitandsetLayout)- Added
bar.shadowImage = UIImage()on everyUITabBarinstance as belt-and-suspenders against iOS 26 ignoring the appearance-level shadow override
- Root cause: in v1.3.3 the
-
Fixed: Liquid Glass halo rendering outside platform-view bounds during iOS route transitions — the "placeholder square" reported across CNButton, CNTabBar, and other widgets (Issue #29)
- Root cause: iOS 26 Liquid Glass effects (
UIButton.Configuration.glass(),UITabBarglass material,.glassEffect()SwiftUI modifier) render a translucent halo that extends slightly outside the view's frame. Without containment, that halo became visible during route transitions as a square outline around the widget on the outgoing page. - Same containment pattern as Issue #2 applied to:
CNButton,CNPopupMenuButton,CNSearchBar,CNFloatingIsland,CNLiquidGlassContainer container.clipsToBounds = true,container.layer.shadowOpacity = 0,container.layer.backgroundColor = clear,container.isOpaque = falseplus the same on the inner subview where applicable
- Root cause: iOS 26 Liquid Glass effects (
-
Fixed:
CNTabBarwithsearchItem— the floating Liquid Glass search orb's top edge was being cropped (commented in Issue #31 by @el2zay)- The iOS 26+ search-tab-bar variant (
CupertinoTabBarSearchPlatformView) now leaves its container un-clipped so the search orb can render its top edge correctly. Top-edge hairline is still suppressed viabar.shadowImage = UIImage()andbar.layer.shadowOpacity = 0, so the original Issue #2 shadow bleed does not return.
- The iOS 26+ search-tab-bar variant (
-
Fixed:
CNGlassButtonGroupbadge X-position — the last badge floated near the screen edge instead of sitting on its button when buttons were centered as a tight pill inside a wider containerupdateBadgePositions()now estimates each button's rendered width from icon size + padding + minHeight (capsule width = max(intrinsic, minHeight)), computes the centered-HStack starting offset, and places each badge at the actual button's top-right corner instead of dividing container width evenly across button count.
-
Fixed:
CNGlassButtonGroupAuto Layout_UITemporaryLayoutWidth = 0warning on initial mount — silenced by lowering hosting-view constraint priorities to.defaultHighso UIKit can break them silently during the brief temp-width=0 phase without logging.
Test demos added #
Testing → #2: Modal bottom sheet shadow— opens aCupertinoModalPopupover aCNTabBarso the top-edge shadow bleed (or its absence) is easy to inspectTesting → #29: Per-widget halo test— one slow-transition push per widget so each can be verified in isolationTesting → CNTabBar split-search clip—CNTabBarwith asearchItemso the floating orb is easy to inspect at the bottom-rightTesting → #31: TextField disappear in modal— MaterialScaffold+CNTabBar(withsearchItem) inbottomNavigationBar, opens a modal sheet with a MaterialTextFieldand aCupertinoTextFieldfor comparison
Pana #
- 160/160
1.3.9 #
New Features #
- Added:
checkedproperty onCNPopupMenuItemfor checkmark/selected state (Issue #28)- Native iOS uses
UIAction.state = .onfor native checkmark display - Supports single-selection, multi-selection, and mixed checked+disabled states
- Flutter fallback shows a checkmark icon before the label
- Native iOS uses
Bug Fixes #
- Fixed:
PlatformException(recreating_view)on iOS hot restart (PR #30 by @lucakramberger)- New
PlatformViewGuardutility delays platform view creation during startup CNTabBarrefactored from nested FutureBuilders to state-managed async pipeline- Native
deinitcleanup added to tab bar platform views
- New
1.3.8 #
Bug Fixes #
- Fixed: macOS build — resolved 5 compilation errors in native Swift code
badgeCountparameter missing fromsetupSwiftUIButtonmethod.clearcolor inference onCALayer.backgroundColor(now usesNSColor.clear.cgColor)FlutterPlatformViewprotocol replaced withNSViewfor LiquidGlassContainer- Removed invalid
namespaceargument fromGlassButtonSwiftUIcall NSButton.titlenon-optional handling
1.3.7 #
New Features #
- Added: Popup menu button support in
CNGlassButtonGroupviaCNButtonData.popup()(PR #23 by @byackee)- Mix regular icon buttons and popup menu buttons in the same glass button group
- Native SwiftUI rendering with
UIMenusupport CNButtonDataPopupItemmodel for popup menu items
- Added:
labelFontFamilyandlabelFontSizeproperties forCNTabBar(PR #26, Issue #16 by @byackee)- Customize tab bar label font with any registered font family
- Dynamic font updates via method channel
Bug Fixes #
- Fixed:
buttonCustomIconColornow works on iOS 26 with Liquid Glass rendering (PR #24, Issue #21 by @byackee)- Color is now sent to the native side via
capturedButtonIconColor - Native side applies
.withTintColor(.alwaysOriginal)to preserve custom icon colors - Menu item
iconColoralso supported for customIconDataicons
- Color is now sent to the native side via
- Fixed:
CNSwitchno longer pushed upward by keyboard (PR #25, Issue #4 by @byackee)- iOS 16.4+: uses
safeAreaRegions.remove(.keyboard)official API - Pre-iOS 16.4: runtime fix targeting the hosting view's private keyboard notification handler
- iOS 16.4+: uses
1.3.6 #
Bug Fixes #
- Fixed: Horizontal glass button group "waist" effect — reduced toolbar shrinkage between adjacent buttons by enforcing minimum 80pt glass spacing for horizontal groups with 2+ buttons (PR #20 by @byackee)
- Fixed:
CNTabBarnow shows a Flutter fallback tab bar while the native view initializes, instead of blank space for ~2 seconds (Issue #5) - Fixed:
CNTabBarwith 5 items no longer has sporadic missing labels — added a second refresh pass for slow-to-initialize native views (Issue #6)
Improvements #
- Added: macOS podspec for CocoaPods support (Issue #10)
1.3.5 #
Bug Fixes #
- Fixed:
CNTabBariconSizenow correctly applies toCNImageAsset(SVG/image) icons (Issue #19)- Previously, only SF Symbol icons respected the
iconSizeproperty - Image assets loaded via
loadFlutterAssetandcreateImageFromDatanow receive the size parameter
- Previously, only SF Symbol icons respected the
1.3.4 #
New Features #
- Added:
interactionproperty forCNButtonConfigandCNButtonDataConfig(PR #15 by @anirudhrao-github)- Allows disabling button touch handling without changing visual appearance
- When
interaction: false, button maintains normal look but doesn't respond to touches - Useful for conditional interactivity while preserving UI consistency
Improvements #
- Improved:
LiquidGlassContainerlayout simplified for better parent alignment control
1.3.3 #
New Features #
-
Added:
customIconSizeproperty forCNButtonConfigandCNButtonDataConfig(PR #12 by @anirudhrao-github)- Allows customizing the size of custom icons (IconData) in buttons
- Previously hardcoded to 20.0 points, now configurable
-
Added:
iconSizeproperty forCNTabBarto control SF Symbol icon sizes- Supports dynamic icon sizing with automatic height adjustment
- Note: Icons above 30pt may have minor visual quirks due to UITabBar constraints
Bug Fixes #
-
Fixed:
CNGlassButtonGroupno longer forces equal width on all buttons (PR #12 by @anirudhrao-github)- Buttons now use their intrinsic width based on content
- Label buttons can now be wider than icon-only buttons in the same group
- Uses SwiftUI
.fixedSize(horizontal: true, vertical: false)for proper sizing
-
Fixed:
CNTabBar.onTapnow fires for reselects (Issue #13)- Previously, tapping the already-selected tab did not trigger the callback
- Now all taps fire
onTap, allowing scroll-to-top or navigation reset on reselect
-
Fixed:
CNTabBaricon clipping on iOS 26+ Liquid Glass- Disabled
clipsToBoundson iOS 26+ to allow proper Liquid Glass pill overflow - Tab bar height now adjusts dynamically based on icon size
- Disabled
Improvements #
- Improved: Initial layout rendering for
CNGlassButtonGroup- Added immediate layout pass after view creation for correct first render
1.3.2 #
New Features #
- Added: Badge support for
CNGlassButtonGroupicon buttons (PR #11 by @anirudhrao-github)- New
badgeCountproperty onCNButtonData.icon()for displaying notification badges - Badges display as red circles with white text, showing "99+" for counts over 99
- Uses UIKit overlay on iOS to prevent glass effect sampling artifacts
- Proper clipping during page transitions
- New
Improvements #
-
Improved: Added library-level documentation for better API discoverability
- Enhanced dartdoc comments for
button,button_data,button_style, andcupertino_nativelibraries - 91.4% API documentation coverage
- Enhanced dartdoc comments for
-
Fixed: Dart formatting issues for pub.dev compliance
- Resolved formatting in
button.dartandglass_button_group.dart - Achieves 160/160 pana score
- Resolved formatting in
1.3.1 #
Bug Fixes #
- Fixed: Tint color now works correctly when buttons are inside
CNGlassButtonGroup(PR #8 by @anirudhrao-github)- Previously, button tint colors were ignored when placed inside grouped glass buttons
- Now buttons properly inherit and display their configured tint colors within button groups
1.3.0 #
New Features #
-
Added:
CNTabBarNative- Native iOS 26 Tab Bar with full UITabBarController integration- Uses native
UITabBarController+UISearchControllerfor authentic iOS 26 liquid glass effects CNTabBarNative.enable()/CNTabBarNative.disable()for app-level tab bar managementCNTabclass for tab configuration with SF Symbols and search tab support- Callbacks:
onTabSelected,onSearchChanged,onSearchSubmitted,onSearchCancelled,onSearchActiveChanged - Full badge count support and dynamic styling
- Uses native
-
Added:
CNSearchScaffold- Native search scaffold controller for standalone search UI -
Added:
CNToast- Toast notification widget with Liquid Glass effects- Static methods:
show(),success(),error(),warning(),info(),loading() - Duration presets: short (2s), medium (3.5s), long (5s)
- Position options: top, center, bottom
- Auto-dismiss with queue management
CNLoadingToastHandlefor dismissing loading toasts
- Static methods:
-
Added:
labelproperty toCNTabBarSearchItemfor customizing the search tab label- Defaults to 'Search' to match iOS native behavior
-
Added:
preserveTopToBottomOrderproperty toCNPopupMenuButton(Issue #3)- When
true, menu items maintain top-to-bottom order (1,2,3,4) regardless of menu direction - Default
falsepreserves native iOS behavior where item 1 stays closest to the button - Uses
UIDeferredMenuElement.uncachedfor dynamic position detection
- When
Improvements #
-
Enhanced:
PlatformVersionnow auto-initializes on first access- No longer need to call
await PlatformVersion.initialize()inmain() - Just use
PlatformVersion.isIOS26OrLaterdirectly - Old
initialize()method kept for backwards compatibility (marked deprecated)
- No longer need to call
-
Added: New helper properties to
PlatformVersion:isIOS,isMacOS,isAndroid,isAppleisIOSVersionInRange(min, max),isMacOSVersionInRange(min, max)
Bug Fixes #
-
Fixed:
CNPopupMenuButton.iconnow respects the order defined in items (Issue #3)- Added
preserveTopToBottomOrderparameter to control item ordering behavior - Native iOS behavior keeps first item closest to button; set
preserveTopToBottomOrder: truefor consistent top-to-bottom order
- Added
-
Fixed: Tab bar shadow artifact appearing over modals and bottom sheets (Issue #2)
- Changed
configureWithDefaultBackground()toconfigureWithTransparentBackground() - Added explicit shadow removal:
shadowColor = .clear,shadowImage = UIImage() - Added
container.clipsToBounds = trueandlayer.shadowOpacity = 0
- Changed
-
Fixed: Search bar keyboard auto-opening behavior (Issue #1)
automaticallyActivatesSearch: falsenow properly prevents keyboard from auto-opening- This is native iOS behavior - the search bar expands but keyboard only opens on text field tap
1.2.0 #
New Features #
-
Added: iOS 26 Search Tab Feature for CNTabBar with animated Liquid Glass expansion
- Native
UISearchTab-style search integration that follows Apple's iOS 26 design - Search button expands into a full search bar with smooth spring animation
- Tabs collapse to icon-only mode when search is active
- Full Flutter fallback for iOS < 26 with identical behavior
- Native
-
Added:
CNTabBarSearchItemconfiguration class for search tab customizationplaceholder: Custom placeholder text for the search fieldonSearchChanged: Callback for live filtering as user typesonSearchSubmit: Callback when user submits searchonSearchActiveChanged: Callback for expand/collapse state changesautomaticallyActivatesSearch: Control keyboard auto-activation behavior
-
Added:
CNTabBarSearchStylefor visual customization- Icon sizes, colors, and active states
- Search bar dimensions, padding, and border radius
- Animation duration control
- Clear button visibility toggle
-
Added:
CNTabBarSearchControllerfor programmatic search controlactivateSearch()/deactivateSearch(): Expand/collapse search programmaticallytextproperty: Get/set search textclear(): Clear search text with optional deactivation- Listener support for reactive state management
Improvements #
- Enhanced:
automaticallyActivatesSearchnow properly controls keyboard behavior- When
false: Search bar expands but keyboard only opens when user taps the text field - When
true(default): Keyboard opens automatically when search expands - Mirrors
UISearchTab.automaticallyActivatesSearchfrom UIKit
- When
Bug Fixes #
- Fixed:
MissingPluginExceptionerrors during hot reload forsetItemsandrefreshmethods- Added try-catch error handling to prevent crashes during development
- Search view now handles all expected method channel calls
1.1.9 #
New Features #
- Added: Lightweight
setBadgesmethod for CNTabBar to update badge values without rebuilding the entire tab bar- Previously, badge updates required recreating all tab bar items which caused visible flicker
- New implementation only updates
badgeValueon existing UITabBarItems for smooth, instant badge changes - Automatically detected when only badges changed (not labels, icons, or symbols) and uses fast path
Improvements #
- Optimized: CNTabBar now detects badge-only updates in
_syncPropsToNativeIfNeeded()and calls lightweight nativesetBadgesmethod instead of fullsetItemsrebuild - Performance: Badge updates are now instant with no view recreation or animation interruption
1.1.7 #
Fixes #
-
Fixed: Split mode tab selection bug where the wrong tab appeared selected on first load
- Issue: When using
split: truein CNTabBar, the right bar (e.g., Rewards tab) would incorrectly appear selected even when the left bar tab (e.g., Discover) was actually selected - Root Cause: In the
refreshmethod, when restoring selection after cycling through tabs for label rendering, the code was incorrectly settingright.selectedItem = rightItems.firstwhenrightOriginalwas nil - Solution: Changed to restore the original selection directly (
right.selectedItem = rightOriginal), which correctly keeps the right bar unselected when a left bar tab is active
- Issue: When using
-
Fixed: Added
setSelectedIndexcall afterrefreshin Flutter widget to ensure correct selection state after view initialization
1.1.5 #
Breaking Changes #
- iOS Minimum Version: Raised iOS deployment target from 13.0 to 15.0
- Required for
@FocusStateand other iOS 15+ SwiftUI features - Most production apps already target iOS 15+ (released September 2021)
- Required for
Fixes #
- Fixed: Swift compiler error
'FocusState' is only available in iOS 15.0 or newer - Fixed: Swift compiler error
'self' used before 'super.init' callin CNSearchBar - Fixed: Pod installation issues when used in projects with iOS 15+ deployment target
1.1.3 #
Fixes #
- Fixed: Full 50/50 pub.dev static analysis score (160/160 pana points)
- Fixed: All remaining lint and formatting issues
1.1.0 #
Documentation Overhaul #
- Added: Complete documentation for all widgets with real iOS 26 screenshots
- Added: CNSwitch documentation with controller examples
- Added: CNPopupMenuButton documentation with text and icon variants
- Added: CNSegmentedControl documentation with SF Symbols support
- Added: Button Styles Gallery showcasing multiple button styles
- Added: Popup menu opened state preview image
- Enhanced: Features table with Controller column
- Enhanced: All images now use centered alignment for better presentation
New Screenshots #
- Real iOS 26 Liquid Glass component screenshots (replacing AI-generated placeholders)
- Button styles gallery (4 preview images)
- Switch, Slider, Popup Menu, Segmented Control, Tab Bar previews
- Popup menu opened state preview
Test Suite Updates #
- Added: Comprehensive widget tests for CNSearchBar, CNFloatingIsland, CNGlassButtonGroup
- Added: Controller tests for CNSearchBarController, CNFloatingIslandController, CNSliderController
- Added: Data model tests for CNButtonData, CNButtonDataConfig, CNSymbol, CNImageAsset
- Updated: Platform and method channel tests with error handling and null response tests
- Updated: Enum tests for all new enums (CNGlassEffect, CNGlassEffectShape, CNSpotlightMode, etc.)
- Total: 82 tests covering all major components and APIs
1.0.6 #
Improvements #
- Fixed: Dart formatting issues to achieve full 50/50 static analysis score on pub.dev
- Added: Preview image for pub.dev package page
1.0.5 #
Improvements #
Static Analysis Cleanup
- Fixed: All
use_build_context_synchronouslywarnings by capturing context-derived values before async gaps - Fixed:
dangling_library_doc_commentswarning - Fixed:
unnecessary_library_nameandunnecessary_importwarnings - Improved: Pub points score (static analysis section)
1.0.4 #
Bug Fixes #
CNButton Tap Detection (iOS < 26 Fallback)
- Fixed: Unreliable tap detection in CupertinoButton fallback mode
- Issue: Buttons showed press animation but
onPresseddidn't fire consistently - Solution: Added
minSize: 0to prevent CupertinoButton's internal minimum size from conflicting with SizedBox constraints - Added: Explicit
borderRadiusandpressedOpacityfor better hit testing and visual feedback
1.0.3 #
Bug Fixes #
Critical: iOS 18 Crash Fix
- Fixed: Reverted GestureDetector overlay that caused crash on iOS 18
- Error:
unrecognized selector sent to instance 'onTap:' - Solution: Removed Stack/GestureDetector approach, kept simple CupertinoButton
Icon Button Padding (kept from 1.0.2)
- Fixed: Increased default padding for icon buttons from 4 to 8 pixels
1.0.2 (BROKEN - DO NOT USE) #
Bug Fixes #
CNButton Tap Detection (iOS < 26 Fallback)
- BROKEN: Added GestureDetector overlay that crashed on iOS 18
- Use 1.0.3 instead
Icon Button Padding
- Fixed: Increased default padding for icon buttons from 4 to 8 pixels
- Icons now have proper breathing room from the button border
1.0.1 #
- Pub Points Improvement: Addressed static analysis issues to improve package score.
- Fix: Resolved
use_build_context_synchronouslywarnings across multiple components. - Fix: Replaced deprecated
Color.valueandwithOpacityusages with modern alternatives. - Documentation: Added missing documentation for public members.
1.0.0 #
Major Release - Complete iOS Fallback Fixes
This release addresses critical issues that caused components to malfunction on iOS versions below 26.
Breaking Changes #
- Package renamed from
cupertino_native_plustocupertino_native_better - Main import changed to
package:cupertino_native_better/cupertino_native_better.dart
Bug Fixes #
CNButton Label Disappearing (iOS < 26)
- Fixed: Buttons with both icon AND label now correctly display both elements in fallback mode
- Root Cause:
widget.isIconwas returningtruefor any button with an icon, even if it also had a label - Solution: Changed fallback check to
widget.isIcon && widget.label == nullto only treat truly icon-only buttons as icon-only
CNTabBar Icons Not Showing (iOS < 26)
- Fixed: Tab bar icons now render correctly using CNIcon instead of empty placeholder circles
- Root Cause: Fallback code only checked for
customIcon, ignoring SF Symbols (icon/activeIcon) - Solution: Added
_buildTabIcon()helper that properly handles all icon types with correct priority
CNIcon/CNButton/CNPopupMenuButton Showing "..." (iOS < 26)
- Fixed: All CN components now properly render SF Symbols on older iOS versions
- Root Cause: Components were checking
shouldUseNativeGlass(iOS 26+) for SF Symbol support, but SF Symbols work on iOS 13+ - Solution: Added new
supportsSFSymbolsgetter that always returns true on iOS/macOS
New Features #
- Added
PlatformVersion.supportsSFSymbolsfor checking SF Symbol availability (iOS 13+, macOS 11+) - Comprehensive dartdoc documentation for all public APIs
- Full comparison table with other packages in README
Documentation #
- Complete rewrite of README with feature comparison
- Migration guide from cupertino_native_plus
- Comprehensive code examples for all widgets
0.0.9 #
- Package preparation for public release
- Updated repository URLs
0.0.8 #
- Fixed SF Symbol rendering in fallback mode for CNButton
- Fixed SF Symbol rendering in fallback mode for CNPopupMenuButton
- Added proper imports for CNIcon in button and popup components
0.0.7 #
- Added
supportsSFSymbolsgetter to PlatformVersion - SF Symbols now render natively on all iOS versions (13+), not just iOS 26+
- Separated Liquid Glass support (iOS 26+) from SF Symbol support (iOS 13+)
0.0.6 #
- Dark Mode Support for LiquidGlassContainer: Added automatic dark mode detection and synchronization for LiquidGlassContainer, ensuring the glass effect correctly adapts to Flutter's theme changes
- Gesture Detection Fixes: Fixed gesture handling in LiquidGlassContainer by wrapping platform views in IgnorePointer, preventing the native view from intercepting touch events and allowing child widgets to receive gestures properly
- Brightness Syncing Improvements: Enhanced brightness synchronization for icons and other components, ensuring they automatically update when the system theme changes
0.0.5 #
- Performance Improvements: Added method channel updates for button groups to prevent full rebuilds and eliminate freezes when updating button parameters
- Preserved Animations: Button groups now update smoothly without losing native animations when button properties change (icon, color, image asset, etc.)
- Efficient Updates: Implemented granular updates for individual buttons in groups, only updating changed buttons instead of rebuilding the entire group
- Reactive SwiftUI Updates: Converted button group SwiftUI views to use ObservableObject pattern for efficient reactive updates
- Button Parameter Updates: Individual buttons in groups can now be updated dynamically via method channels without full view rebuilds
0.0.4 #
- PNG Image Support: Added full support for PNG images in all components (buttons, icons, popup menus, tab bars, glass button groups)
- Automatic Asset Resolution: Implemented automatic asset resolution based on device pixel ratio, similar to Flutter's automatic asset selection. The system now automatically selects the appropriate resolution-specific asset (e.g.,
assets/icons/3.0x/checkcircle.pngfor @3x devices) or falls back to the closest bigger size - ImageUtils Consolidation: Consolidated all image loading, format detection, scaling, and tinting logic into a shared
ImageUtils.swiftclass for better code maintainability and consistency - Fixed PNG Rendering: Fixed PNG image rendering issues in buttons and glass button groups
- Fixed Image Orientation: Fixed image flipping issues for both PNG and SVG images when colors are applied
- Made buttonIcon Optional: Made
buttonIconparameter optional inCNPopupMenuButton.iconconstructor, allowing developers to use onlybuttonImageAssetorbuttonCustomIcon - Improved Glass Effect Appearance: Fixed glass effect appearance synchronization with Flutter's theme mode to prevent dark-to-light transitions on initial render
- Enhanced Image Format Detection: Improved automatic image format detection from file extensions and magic bytes
- Better Fallback Handling: Improved fallback behavior when asset paths fail to load, ensuring images still render from provided image bytes
0.0.3 #
- Updated README to showcase all icon types (SVG assets, custom icons, and SF Symbols)
- Added comprehensive examples for all icon types in Button, Icon, Popup Menu Button, and Tab Bar sections
- Added icon support overview at the beginning of "What's in the package" section
- Clarified that all components support multiple icon types with unified priority system
0.0.2 #
- Updated README with corrected version requirements and improved documentation
- Fixed iOS minimum version requirement (13.0 instead of 14.0)
- Removed incorrect Xcode 26 beta requirement
- Added Contributing and License sections
- Improved package description and introduction
0.0.1 #
- Initial release
- Fixed iOS 26+ version detection using Platform.operatingSystemVersion parsing
- Native Liquid Glass widgets for iOS and macOS
- Support for CNButton, CNIcon, CNSlider, CNSwitch, CNTabBar, CNPopupMenuButton, CNSegmentedControl
- Glass effect unioning for grouped buttons
- LiquidGlassContainer for applying glass effects to any widget
