From c5da510d7d50c93475fd09553724ba7c4a302e8b Mon Sep 17 00:00:00 2001 From: Shin Yamamoto Date: Wed, 5 Nov 2025 18:51:01 +0900 Subject: [PATCH 1/4] Enable to build it for iOS 12 --- .../SamplesSwiftUI/UseCases/MainView.swift | 4 ++++ FloatingPanel.xcodeproj/project.pbxproj | 6 +++--- Sources/Core.swift | 18 ++++++++++++++++-- Sources/SwiftUI/FloatingPanelView.swift | 2 +- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Examples/SamplesSwiftUI/SamplesSwiftUI/UseCases/MainView.swift b/Examples/SamplesSwiftUI/SamplesSwiftUI/UseCases/MainView.swift index d0cd5b06..faedc8ac 100644 --- a/Examples/SamplesSwiftUI/SamplesSwiftUI/UseCases/MainView.swift +++ b/Examples/SamplesSwiftUI/SamplesSwiftUI/UseCases/MainView.swift @@ -3,6 +3,7 @@ import FloatingPanel import SwiftUI import UIKit +import os.log struct MainView: View { @State private var panelLayout: FloatingPanelLayout? = MyFloatingPanelLayout() @@ -20,6 +21,9 @@ struct MainView: View { .floatingPanelSurfaceAppearance(.transparent()) .floatingPanelLayout(panelLayout) .floatingPanelState($panelState) + .onChange(of: panelState) { newValue in + Logger().debug("Panel state changed: \(newValue ?? .hidden)") + } VStack(spacing: 32) { Button("Move to full") { diff --git a/FloatingPanel.xcodeproj/project.pbxproj b/FloatingPanel.xcodeproj/project.pbxproj index 7d9486d0..96e7c1de 100644 --- a/FloatingPanel.xcodeproj/project.pbxproj +++ b/FloatingPanel.xcodeproj/project.pbxproj @@ -523,7 +523,7 @@ ENABLE_USER_SCRIPT_SANDBOXING = YES; INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -556,7 +556,7 @@ ENABLE_USER_SCRIPT_SANDBOXING = YES; INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -694,7 +694,7 @@ ENABLE_USER_SCRIPT_SANDBOXING = YES; INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Sources/Core.swift b/Sources/Core.swift index 2d19ac79..659ca72c 100644 --- a/Sources/Core.swift +++ b/Sources/Core.swift @@ -1,6 +1,8 @@ // Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license. +#if canImport(Combine) import Combine +#endif import UIKit import os.log @@ -50,7 +52,13 @@ class Core: NSObject, UIGestureRecognizerDelegate { } } } - private(set) var statePublisher: CurrentValueSubject = .init(.hidden) + + @available(iOS 13.0, *) + private(set) var statePublisher: CurrentValueSubject? { + get { _statePublisher as? CurrentValueSubject } + set { _statePublisher = newValue } + } + private var _statePublisher: Any? var panGestureRecognizer: FloatingPanelPanGestureRecognizer let panGestureDelegateRouter: FloatingPanelPanGestureRecognizer.DelegateRouter @@ -100,6 +108,10 @@ class Core: NSObject, UIGestureRecognizerDelegate { super.init() + if #available(iOS 13.0, *) { + statePublisher = .init(.hidden) + } + panGestureRecognizer.set(floatingPanel: self) surfaceView.addGestureRecognizer(panGestureRecognizer) panGestureRecognizer.addTarget(self, action: #selector(handle(panGesture:))) @@ -262,7 +274,9 @@ class Core: NSObject, UIGestureRecognizerDelegate { layoutAdapter.activateLayout(for: target, forceLayout: true) backdropView.alpha = getBackdropAlpha(for: target) adjustScrollContentInsetIfNeeded() - statePublisher.send(target) + if #available(iOS 13.0, *) { + statePublisher?.send(target) + } } private func getBackdropAlpha(for target: FloatingPanelState) -> CGFloat { diff --git a/Sources/SwiftUI/FloatingPanelView.swift b/Sources/SwiftUI/FloatingPanelView.swift index 972a150c..d7ba4d11 100644 --- a/Sources/SwiftUI/FloatingPanelView.swift +++ b/Sources/SwiftUI/FloatingPanelView.swift @@ -235,7 +235,7 @@ extension FloatingPanelCoordinatorProxy { /// Start observing ``FloatingPanelController/state`` through the `Core` object. func observeStateChanges() { - controller.floatingPanel.statePublisher + controller.floatingPanel.statePublisher? .sink { [weak self] state in guard let self = self else { return } // Needs to update the state binding value on the next run loop cycle to avoid this error. From 411276df5240f5176bc02db093c4d752da648fbf Mon Sep 17 00:00:00 2001 From: Shin Yamamoto Date: Wed, 5 Nov 2025 19:33:33 +0900 Subject: [PATCH 2/4] Add CoreTests.test_statePublisher --- Tests/CoreTests.swift | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Tests/CoreTests.swift b/Tests/CoreTests.swift index 9dfda5e3..3057430b 100644 --- a/Tests/CoreTests.swift +++ b/Tests/CoreTests.swift @@ -1,6 +1,9 @@ // Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license. import XCTest +#if canImport(Combine) +import Combine +#endif @testable import FloatingPanel class CoreTests: XCTestCase { @@ -1022,6 +1025,45 @@ class CoreTests: XCTestCase { XCTAssertEqual(fpc.surfaceLocation(for: .full).y, fpc.surfaceLocation.y) XCTAssertEqual(delegate.willAttract, false) } + + @available(iOS 13.0, *) + func test_statePublisher() throws { + let fpc = FloatingPanelController() + fpc.showForTest() + + XCTAssertEqual(fpc.state, .half) + + // Verify statePublisher is available on iOS 13+ + XCTAssertNotNil(fpc.floatingPanel.statePublisher) + + var receivedStates: [FloatingPanelState] = [] + var cancellables = Set() + + // Subscribe to statePublisher + fpc.floatingPanel.statePublisher? + .sink { state in + receivedStates.append(state) + } + .store(in: &cancellables) + + // The initial state should be emitted first + XCTAssertEqual(receivedStates, [.half]) + + // Move to .full + fpc.move(to: .full, animated: false) + XCTAssertEqual(fpc.state, .full) + XCTAssertEqual(receivedStates, [.half, .full]) + + // Move to .tip + fpc.move(to: .tip, animated: false) + XCTAssertEqual(fpc.state, .tip) + XCTAssertEqual(receivedStates, [.half, .full, .tip]) + + // Move back to .half + fpc.move(to: .half, animated: false) + XCTAssertEqual(fpc.state, .half) + XCTAssertEqual(receivedStates, [.half, .full, .tip, .half]) + } } private class FloatingPanelLayout3Positions: FloatingPanelTestLayout { From 3c670b4d5e805fcecc695413ce081e02882f749a Mon Sep 17 00:00:00 2001 From: Shin Yamamoto Date: Wed, 5 Nov 2025 19:42:02 +0900 Subject: [PATCH 3/4] ci: fix a job error --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3eda5045..476c69ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: fail-fast: false matrix: include: - - os: "26.0" + - os: "26.0.1" xcode: "26.0.1" sim: "iPhone 16 Pro" parallel: NO # Stop random test job failures From 5222c056c666934dbece1c19e5701e06b7ded858 Mon Sep 17 00:00:00 2001 From: Shin Yamamoto Date: Tue, 11 Nov 2025 18:50:47 +0900 Subject: [PATCH 4/4] Set minimum iOS version to v12 --- FloatingPanel.podspec | 3 ++- Package.swift | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/FloatingPanel.podspec b/FloatingPanel.podspec index d42fc058..7f3ef63b 100644 --- a/FloatingPanel.podspec +++ b/FloatingPanel.podspec @@ -11,7 +11,7 @@ The new interface displays the related contents and utilities in parallel as a u s.author = "Shin Yamamoto" s.social_media_url = "https://x.com/scenee" - s.platform = :ios, "13.0" + s.platform = :ios, "12.0" s.source = { :git => "https://github.com/scenee/FloatingPanel.git", :tag => s.version.to_s } s.source_files = "Sources/**/*.swift" s.swift_version = '5.0' @@ -20,3 +20,4 @@ The new interface displays the related contents and utilities in parallel as a u s.license = { :type => "MIT", :file => "LICENSE" } end + diff --git a/Package.swift b/Package.swift index 8f86014a..d6b6c5c6 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "FloatingPanel", platforms: [ - .iOS(.v13) + .iOS(.v12) ], products: [ // Products define the executables and libraries produced by a package, and make them visible to other packages. @@ -25,3 +25,4 @@ let package = Package( ], swiftLanguageVersions: [.version("5")] ) +