From 1014bf25a30223d35bf10ff4b0496c6dde3c88f3 Mon Sep 17 00:00:00 2001 From: Muukii Date: Wed, 8 Jul 2026 19:24:23 +0900 Subject: [PATCH 1/2] Update configuration --- .../FluidPictureInPictureController.swift | 51 ++++++++++++++++--- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/Sources/FluidPictureInPicture/FluidPictureInPictureController.swift b/Sources/FluidPictureInPicture/FluidPictureInPictureController.swift index 10c618f24..588a6fa95 100644 --- a/Sources/FluidPictureInPicture/FluidPictureInPictureController.swift +++ b/Sources/FluidPictureInPicture/FluidPictureInPictureController.swift @@ -8,6 +8,16 @@ import GeometryKit */ open class FluidPictureInPictureController: FluidWrapperViewController { + private let configuration: Configuration + + public init( + configuration: Configuration = .init(), + content: Content? = nil + ) { + self.configuration = configuration + super.init(content: content) + } + public final var state: State { customView.state } @@ -17,7 +27,7 @@ open class FluidPictureInPictureController: FluidWrapperViewController { } public final override func loadView() { - view = View() + view = View(configuration: configuration) } open override func viewDidLoad() { @@ -58,8 +68,22 @@ extension FluidPictureInPictureController { case hiding } + /// Layout values used while the content is floating above its background. public struct Configuration { + /// The fixed size used for the floating content container. + public var sizeForFloating: CGSize + + /// The spacing between the floating content and the visible/safe screen edges. + public var floatingContentInset: CGFloat + + public init( + sizeForFloating: CGSize = .init(width: 100, height: 140), + floatingContentInset: CGFloat = 12 + ) { + self.sizeForFloating = sizeForFloating + self.floatingContentInset = floatingContentInset + } } public final class ContainerView: UIView { @@ -103,7 +127,7 @@ extension FluidPictureInPictureController { let containerView: ContainerView = .init() - let sizeForFloating = CGSize(width: 100, height: 140) + let configuration: Configuration let safeAreaFinder: SafeAreaFinder private(set) var state: State = .init() { @@ -121,10 +145,12 @@ extension FluidPictureInPictureController { } } - override init( - frame: CGRect + init( + configuration: Configuration, + frame: CGRect = .zero ) { + self.configuration = configuration self.safeAreaFinder = .init(windowScene: nil) super.init(frame: frame) @@ -262,13 +288,16 @@ extension FluidPictureInPictureController { for state: State ) -> CGRect { - let containerSize = sizeForFloating + let containerSize = configuration.sizeForFloating let baseFrame = bounds let insetFrame = baseFrame .inset(by: state.inset) - .insetBy(dx: 12, dy: 12) + .insetBy( + dx: configuration.floatingContentInset, + dy: configuration.floatingContentInset + ) var origin = CGPoint(x: 0, y: 0) @@ -294,8 +323,9 @@ extension FluidPictureInPictureController { func setIsHidden(_ isHidden: Bool, animated: Bool) { - let animator = UIViewPropertyAnimator(duration: 0.6, dampingRatio: 0.8) { [self] in + containerView.isUserInteractionEnabled = !isHidden + let updates = { [self] in if isHidden { containerView.alpha = 0 containerView.transform = .init(scaleX: 0.8, y: 0.8) @@ -305,7 +335,12 @@ extension FluidPictureInPictureController { } } - animator.startAnimation() + if animated { + let animator = UIViewPropertyAnimator(duration: 0.6, dampingRatio: 0.8, animations: updates) + animator.startAnimation() + } else { + updates() + } } From c26028655a96cfc1ca8d543a0565333b5d6114e8 Mon Sep 17 00:00:00 2001 From: Muukii Date: Wed, 15 Jul 2026 00:24:19 +0900 Subject: [PATCH 2/2] Update --- .../DemoPictureInPictureController.swift | 21 +---- .../DemoPictureInPictureCoolController.swift | 15 ---- .../FluidPictureInPictureController.swift | 77 +++++-------------- 3 files changed, 20 insertions(+), 93 deletions(-) diff --git a/Development/Sources/FluidInterfaceKit-Demo/DemoPictureInPictureController.swift b/Development/Sources/FluidInterfaceKit-Demo/DemoPictureInPictureController.swift index b00f52fc0..a9a4fe06e 100644 --- a/Development/Sources/FluidInterfaceKit-Demo/DemoPictureInPictureController.swift +++ b/Development/Sources/FluidInterfaceKit-Demo/DemoPictureInPictureController.swift @@ -47,18 +47,7 @@ final class DemoPictureInPictureController: FluidPictureInPictureController { ) interactiveView.handlers.onTap = { [unowned self] in - - switch state.mode { - case .maximizing: - setMode(.floating) - case .folding: - break - case .floating: - setMode(.maximizing) - case .hiding: - break - } - + setMode(.hiding) } setContent(interactiveView) @@ -70,18 +59,10 @@ final class DemoPictureInPictureController: FluidPictureInPictureController { setMode(.hiding) } - UIButton.make(title: "maximizing") { [unowned self] in - setMode(.maximizing) - } - UIButton.make(title: "floating") { [unowned self] in setMode(.floating) } - UIButton.make(title: "folding") { [unowned self] in - setMode(.folding) - } - UIButton.make(title: "Add safeArea") { [unowned self] in additionalSafeAreaInsets.bottom += 20 } diff --git a/Development/Sources/FluidInterfaceKit-Demo/DemoPictureInPictureCoolController.swift b/Development/Sources/FluidInterfaceKit-Demo/DemoPictureInPictureCoolController.swift index ba9e4ed43..940f0e90f 100644 --- a/Development/Sources/FluidInterfaceKit-Demo/DemoPictureInPictureCoolController.swift +++ b/Development/Sources/FluidInterfaceKit-Demo/DemoPictureInPictureCoolController.swift @@ -46,21 +46,6 @@ final class DemoPictureInPictureCoolController: FluidPictureInPictureController content.layer.masksToBounds = true content.layer.cornerRadius = 8 - interactiveView.handlers.onTap = { [unowned self] in - - switch state.mode { - case .maximizing: - setMode(.floating) - case .folding: - break - case .floating: - setMode(.maximizing) - case .hiding: - break - } - - } - setContent(interactiveView) } diff --git a/Sources/FluidPictureInPicture/FluidPictureInPictureController.swift b/Sources/FluidPictureInPicture/FluidPictureInPictureController.swift index 588a6fa95..02673e961 100644 --- a/Sources/FluidPictureInPicture/FluidPictureInPictureController.swift +++ b/Sources/FluidPictureInPicture/FluidPictureInPictureController.swift @@ -4,7 +4,7 @@ import FluidCore import GeometryKit /** - A container view controller that manages a view to be floating, maximizing, hiding, etc. + A container view controller that presents content as a movable picture-in-picture view. */ open class FluidPictureInPictureController: FluidWrapperViewController { @@ -62,8 +62,6 @@ extension FluidPictureInPictureController { } public enum Mode { - case maximizing - case folding case floating case hiding } @@ -161,7 +159,12 @@ extension FluidPictureInPictureController { addSubview(containerView) - NotificationCenter.default.addObserver(self, selector: #selector(handleInsetsUpdate), name: SafeAreaFinder.notificationName, object: nil) + NotificationCenter.default.addObserver( + self, + selector: #selector(handleInsetsUpdate), + name: SafeAreaFinder.notificationName, + object: nil + ) } required init?( @@ -188,76 +191,34 @@ extension FluidPictureInPictureController { func setMode(_ mode: Mode, animated: Bool) { state.mode = mode setNeedsLayout() + layoutIfNeeded() switch mode { - case .maximizing: - setIsHidden(false, animated: animated) - case .folding: - setIsHidden(false, animated: animated) case .floating: setIsHidden(false, animated: animated) case .hiding: setIsHidden(true, animated: animated) } - - if animated { - - let animator = UIViewPropertyAnimator( - duration: 0.6, - timingParameters: UISpringTimingParameters( - dampingRatio: 0.9, - initialVelocity: .zero - ) - ) - - animator.addAnimations { - self.layoutIfNeeded() - } - - animator.startAnimation() - - } else { - - layoutIfNeeded() - - } - } override func layoutSubviews() { super.layoutSubviews() - switch state.mode { - case .hiding: - break - case .maximizing: - containerView.frame = bounds - state.conditionToLayout = nil - case .folding: - break - case .floating: - let proposedCondition = State.ConditionToLayout( - bounds: bounds, - inset: state.inset, - safeAreaInsets: safeAreaInsets, - layoutMargins: layoutMargins - ) - - switch state.conditionToLayout { - case .some(let condition) where condition != proposedCondition: - state.conditionToLayout = proposedCondition - case .none: - state.conditionToLayout = proposedCondition - default: - - return - } + let proposedCondition = State.ConditionToLayout( + bounds: bounds, + inset: state.inset, + safeAreaInsets: safeAreaInsets, + layoutMargins: layoutMargins + ) - Fluid.setFrameAsIdentity(calculateFrameForFloating(for: state), for: containerView) + guard state.conditionToLayout != proposedCondition else { + return } + state.conditionToLayout = proposedCondition + Fluid.setFrameAsIdentity(calculateFrameForFloating(for: state), for: containerView) } - + override func didMoveToWindow() { super.didMoveToWindow()