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 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.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/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/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")] ) + 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. 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 {