[3.0.0] - 2026-08-20
Fluidable now builds in the Swift 6 language mode. Adopting this release
requires source changes in any project that conforms to Fluidable's delegate
protocols or matches on FluidCoreAnimatorError.
Minimum requirements are unchanged: iOS 15.0 or later, built with Xcode 26.
Breaking Changes
1. Delegate protocols and view-facing types are now @MainActor
FluidDelegate is annotated with @MainActor, so every protocol that inherits
from it is main-actor isolated as well:
FluidTransitionConfigurationDelegateFluidTransitionActionDelegateFluidTransitionSourceConfigurationDelegateFluidTransitionDestinationConfigurationDelegateFluidTransitionSourceActionDelegateFluidTransitionDestinationActionDelegateFluidNavigationConfigurationDelegateFluidNavigationActionDelegateFluidNavigationSourceConfigurationDelegateFluidNavigationDestinationConfigurationDelegateFluidNavigationSourceActionDelegateFluidNavigationDestinationActionDelegate
The following declarations are annotated directly:
FluidResizableTransitionDelegateFluidNavigationBarCompatibleFluidFrameDimensionCompatibleFluidInteractiveViewFluidBackgroundCompatibleFluidAnimatorCompatibleFluidCoreAnimatorLayerConvertibleAdaptiveElementAdaptiveInterfaceFluidNavigationControllerDelegateFluidViewControllerTransitioningDelegateFluidLayout
Conforming types must be main-actor isolated. UIViewController and UIView
subclasses already are, so most conformances keep compiling unchanged. A
conformance declared on a type that is not isolated needs an explicit
annotation:
// Before (2.x)
final class TransitionCoordinator: NSObject, FluidTransitionSourceConfigurationDelegate {
func transitionPresentationStyle(from source: FluidSourceViewController,
to destination: FluidDestinationViewController,
with navigation: FluidNavigationController?) -> FluidTransitionStyle {
return .fluid(behavior: .all)
}
}
// After (3.0.0)
@MainActor
final class TransitionCoordinator: NSObject, FluidTransitionSourceConfigurationDelegate {
func transitionPresentationStyle(from source: FluidSourceViewController,
to destination: FluidDestinationViewController,
with navigation: FluidNavigationController?) -> FluidTransitionStyle {
return .fluid(behavior: .all)
}
}Any access to these types from a non-isolated context now has to hop to the
main actor, for example with await MainActor.run { ... } or by marking the
calling function @MainActor.
2. FluidCoreAnimatorError.invalidArgument carries String instead of Any?
The from and to payloads changed from Any? to String so that the error
is Sendable. The values are formatted with String(describing:) before the
error is thrown, so the rendered text is unchanged.
// Before (2.x)
case .invalidArgument(let id, let key, let from, let to):
print(id, key, String(describing: from), String(describing: to))
// After (3.0.0)
case .invalidArgument(let id, let key, let from, let to):
print(id, key, from, to)3. FluidRoundCornerStyle.all raw value changed from 11 to 15
all was defined as [.top, .right, .left, .left], which omitted .bottom
and duplicated .left. As a result all.contains(.bottom) returned false
and its description dropped bottom. It is now [.top, .right, .bottom, .left].
// Before (2.x)
FluidRoundCornerStyle.all.rawValue // 11
FluidRoundCornerStyle.all.contains(.bottom) // false
// After (3.0.0)
FluidRoundCornerStyle.all.rawValue // 15
FluidRoundCornerStyle.all.contains(.bottom) // trueIf you persisted the raw value of all, remap 11 to 15 when reading it
back:
let stored: Int = defaults.integer(forKey: "cornerStyle")
let style: FluidRoundCornerStyle = stored == 11 ? .all : .init(rawValue: stored)4. roundingCorners and maskedCorners now honour arbitrary combinations
Both accessors matched self against the five named constants with a switch.
Because switch on an OptionSet is exact equality, any other combination fell
through to default and produced no rounding. They now union the corner sets of
every flag they contain, so combinations that were silently ignored start taking
effect.
let style: FluidRoundCornerStyle = [.top, .right]
// Before (2.x)
style.roundingCorners // nil
style.maskedCorners // []
// After (3.0.0)
style.roundingCorners // [.topLeft, .topRight, .bottomRight]
style.maskedCorners // [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]roundingCorners still returns UIRectCorner.allCorners when all four corners
are selected, because allCorners has a raw value of ~0 and is not equal to
the union of the four individual corners.
Added
Sendableconformance for the public value types:FluidDriverType,
FluidAnimationType,FluidBackgroundStyle,FluidDriverInteractionType,
FluidNavigationStyle,FluidPresentationStyle,FluidSlideDirection,
FluidDrawerPosition,FluidTransitionStyle,FluidGestureDirection,
PennerEasing,FluidAnimatorEasing,FluidAnimatorState,
FluidInteractionBehavior,FluidRoundCornerStyle, andFluidGestureAxis.PrivacyInfo.xcprivacyprivacy manifest, shipped with both the SwiftPM
resource bundle and the Xcode framework target. Fluidable collects no data
and uses no required-reason API, so every key is declared empty.
Changed
- The library, the test target, the example app, and the UI test target all
build in the Swift 6 language mode.
Fixed
FluidRoundCornerStyle.allnow includes.bottom. See breaking change 3.roundingCornersandmaskedCornersnow round the corners of any flag
combination instead of only the five named constants. See breaking change 4.
What's Changed
- chore: remove legacy Jazzy/Carthage-era artifacts by @futamura in #43
- Migrate Fluidable library to Swift 6 language mode by @futamura in #44
- Include bottom corner in FluidRoundCornerStyle.all by @futamura in #45
- Adopt Swift 6 language mode for FluidableTests by @futamura in #46
- Adopt Swift 6 language mode for Example and UITests by @futamura in #47
- feat(privacy): add PrivacyInfo.xcprivacy to library target by @futamura in #48
- build(deps): add Dependabot configuration by @futamura in #49
- docs: add .spi.yml pointing to the GitHub Pages documentation by @futamura in #50
- chore(release): prepare 3.0.0 by @futamura in #51
- Release 3.0.0 by @futamura in #52
Full Changelog: 2.0.0...3.0.0