Skip to content

Commit 52b0f7f

Browse files
authored
Merge pull request #675 from scenee/iss-672
Support dynamic content updates in SwiftUI
2 parents 41d7580 + 7cd4149 commit 52b0f7f

5 files changed

Lines changed: 126 additions & 55 deletions

File tree

.github/workflows/ci.yml

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
name: ci
22

3-
on:
3+
on:
44
push:
5-
branches:
5+
branches:
66
- master
77
pull_request:
8-
branches:
9-
- '*'
10-
workflow_dispatch:
8+
branches:
9+
- "*"
10+
workflow_dispatch:
1111

1212
jobs:
1313
build:
@@ -40,33 +40,33 @@ jobs:
4040
- os: "26.0.1"
4141
xcode: "26.0.1"
4242
sim: "iPhone 16 Pro"
43-
parallel: NO # Stop random test job failures
43+
parallel: NO # Stop random test job failures
4444
runs-on: macos-15
4545
- os: "18.5"
4646
xcode: "16.4"
4747
sim: "iPhone 16 Pro"
48-
parallel: NO # Stop random test job failures
48+
parallel: NO # Stop random test job failures
4949
runs-on: macos-15
5050
- os: "17.5"
5151
xcode: "15.4"
5252
sim: "iPhone 15 Pro"
53-
parallel: NO # Stop random test job failures
53+
parallel: NO # Stop random test job failures
5454
runs-on: macos-14
5555
steps:
56-
- uses: actions/checkout@v4
57-
- name: Testing in iOS ${{ matrix.os }}
58-
run: |
59-
xcodebuild clean test \
60-
-workspace FloatingPanel.xcworkspace \
61-
-scheme FloatingPanel \
62-
-destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}' \
63-
-parallel-testing-enabled '${{ matrix.parallel }}'
56+
- uses: actions/checkout@v4
57+
- name: Testing in iOS ${{ matrix.os }}
58+
run: |
59+
xcodebuild clean test \
60+
-workspace FloatingPanel.xcworkspace \
61+
-scheme FloatingPanel \
62+
-destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}' \
63+
-parallel-testing-enabled '${{ matrix.parallel }}'
6464
timeout-minutes: 20
6565

6666
example:
6767
runs-on: macos-15
6868
env:
69-
DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer
69+
DEVELOPER_DIR: /Applications/Xcode_26.0.1.app/Contents/Developer
7070
strategy:
7171
fail-fast: false
7272
matrix:
@@ -78,13 +78,13 @@ jobs:
7878
- example: "SamplesObjC"
7979
- example: "SamplesSwiftUI"
8080
steps:
81-
- uses: actions/checkout@v4
82-
- name: Building ${{ matrix.example }}
83-
run: |
84-
xcodebuild clean build \
85-
-workspace FloatingPanel.xcworkspace \
86-
-scheme ${{ matrix.example }} \
87-
-sdk iphonesimulator
81+
- uses: actions/checkout@v4
82+
- name: Building ${{ matrix.example }}
83+
run: |
84+
xcodebuild clean build \
85+
-workspace FloatingPanel.xcworkspace \
86+
-scheme ${{ matrix.example }} \
87+
-sdk iphonesimulator
8888
8989
swiftpm:
9090
runs-on: ${{ matrix.runs-on }}
@@ -93,7 +93,7 @@ jobs:
9393
strategy:
9494
fail-fast: false
9595
matrix:
96-
xcode: ["16.4", "15.4"]
96+
xcode: ["26.0.1", "16.4", "15.4"]
9797
platform: [iphoneos, iphonesimulator]
9898
arch: [x86_64, arm64]
9999
exclude:
@@ -119,20 +119,20 @@ jobs:
119119
sys: "ios17.2-simulator"
120120
runs-on: macos-14
121121
steps:
122-
- uses: actions/checkout@v4
123-
- name: "Swift Package Manager build"
124-
run: |
125-
xcrun swift build \
126-
--sdk "$(xcrun --sdk ${{ matrix.platform }} --show-sdk-path)" \
127-
-Xswiftc "-target" -Xswiftc "${{ matrix.arch }}-apple-${{ matrix.sys }}"
122+
- uses: actions/checkout@v4
123+
- name: "Swift Package Manager build"
124+
run: |
125+
xcrun swift build \
126+
--sdk "$(xcrun --sdk ${{ matrix.platform }} --show-sdk-path)" \
127+
-Xswiftc "-target" -Xswiftc "${{ matrix.arch }}-apple-${{ matrix.sys }}"
128128
129129
cocoapods:
130130
runs-on: macos-15
131131
env:
132-
DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer
132+
DEVELOPER_DIR: /Applications/Xcode_26.0.1.app/Contents/Developer
133133
steps:
134-
- uses: actions/checkout@v4
135-
- name: "CocoaPods: pod lib lint"
136-
run: pod lib lint --allow-warnings --verbose
137-
- name: "CocoaPods: pod spec lint"
138-
run: pod spec lint --allow-warnings --verbose
134+
- uses: actions/checkout@v4
135+
- name: "CocoaPods: pod lib lint"
136+
run: pod lib lint --allow-warnings --verbose
137+
- name: "CocoaPods: pod spec lint"
138+
run: pod spec lint --allow-warnings --verbose

Examples/SamplesSwiftUI/SamplesSwiftUI/UseCases/MainView.swift

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,28 @@ import UIKit
66
import os.log
77

88
struct MainView: View {
9+
enum CardContent: String, CaseIterable, Identifiable {
10+
case list
11+
case detail
12+
13+
var id: String { rawValue }
14+
}
915
@State private var panelLayout: FloatingPanelLayout? = MyFloatingPanelLayout()
1016
@State private var panelState: FloatingPanelState?
17+
@State private var selectedContent: CardContent = .list
1118

1219
var body: some View {
1320
ZStack {
1421
Color.orange
1522
.ignoresSafeArea()
16-
.floatingPanel(
17-
coordinator: MyPanelCoordinator.self
18-
) { proxy in
19-
ContentView(proxy: proxy)
20-
}
21-
.floatingPanelSurfaceAppearance(.transparent())
22-
.floatingPanelLayout(panelLayout)
23-
.floatingPanelState($panelState)
24-
.onChange(of: panelState) { newValue in
25-
Logger().debug("Panel state changed: \(newValue ?? .hidden)")
26-
}
27-
2823
VStack(spacing: 32) {
24+
Picker("type", selection: $selectedContent) {
25+
ForEach(CardContent.allCases) {
26+
type in
27+
Text(type.rawValue).tag(type)
28+
}
29+
}
30+
.pickerStyle(.segmented)
2931
Button("Move to full") {
3032
withAnimation(.interactiveSpring) {
3133
panelState = .full
@@ -46,8 +48,37 @@ struct MainView: View {
4648
Text("Switch to My layout")
4749
}
4850
}
51+
Spacer()
4952
}
5053
}
54+
.floatingPanel(
55+
coordinator: MyPanelCoordinator.self
56+
) { proxy in
57+
switch selectedContent {
58+
case .list:
59+
ContentView(proxy: proxy)
60+
case .detail:
61+
HStack {
62+
Spacer()
63+
VStack {
64+
Text("Detail content")
65+
.padding(.top, 32)
66+
Spacer()
67+
}
68+
Spacer()
69+
}
70+
.padding()
71+
.background {
72+
BackgroundView()
73+
}
74+
}
75+
}
76+
.floatingPanelSurfaceAppearance(.transparent())
77+
.floatingPanelLayout(panelLayout)
78+
.floatingPanelState($panelState)
79+
.onChange(of: panelState) { newValue in
80+
Logger().debug("Panel state changed: \(newValue ?? .hidden)")
81+
}
5182
}
5283
}
5384

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import SwiftUI
2+
3+
struct BackgroundView: View {
4+
var body: some View {
5+
GeometryReader { geometry in
6+
Rectangle()
7+
.fill(.clear)
8+
.frame(height: geometry.size.height * 2)
9+
.backgroundEffect()
10+
}
11+
}
12+
}
13+
14+
extension View {
15+
@ViewBuilder
16+
fileprivate func backgroundEffect() -> some View {
17+
if #available(iOS 26, *) {
18+
self.glassEffect(.regular, in: .rect)
19+
} else {
20+
self.background(.regularMaterial)
21+
}
22+
}
23+
}

Examples/SamplesSwiftUI/SamplesSwiftUI/Views/ContentView.swift

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ struct ContentView: View {
1515
.frame(maxWidth: .infinity, alignment: .leading)
1616
.frame(height: 60)
1717
.background(.clear)
18+
.padding(.horizontal)
1819
}
1920
}
2021
}
@@ -28,6 +29,7 @@ struct ContentView: View {
2829
.frame(maxWidth: .infinity, alignment: .leading)
2930
.frame(height: 60)
3031
.background(.clear)
32+
.padding(.horizontal)
3133
}
3234
}
3335
}
@@ -38,12 +40,7 @@ struct ContentView: View {
3840
}
3941
// Prevent revealing underlying content at the bottom of the panel when the panel is moving beyond its fully‑expanded position.
4042
.background {
41-
GeometryReader { geometry in
42-
Rectangle()
43-
.fill(.clear)
44-
.frame(height: geometry.size.height * 2)
45-
.background(.regularMaterial)
46-
}
43+
BackgroundView()
4744
}
4845
}
4946
}

Sources/SwiftUI/FloatingPanelView.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,11 @@ struct FloatingPanelView<MainView: View, ContentView: View>: UIViewControllerRep
110110
_ uiViewController: UIHostingController<MainView>,
111111
context: Context
112112
) {
113+
uiViewController.rootView = main
114+
115+
context.coordinator.updateContent(content(context.coordinator.proxy))
113116
context.coordinator.onUpdate(context: context)
117+
114118
applyEnvironment(context: context)
115119
applyAnimatableEnvironment(context: context)
116120
}
@@ -160,6 +164,9 @@ class FloatingPanelCoordinatorProxy {
160164

161165
private var subscriptions: Set<AnyCancellable> = Set()
162166

167+
// Store a reference to the content hosting controller for dynamic updates
168+
private weak var contentHostingController: UIViewController?
169+
163170
var proxy: FloatingPanelProxy { origin.proxy }
164171
var controller: FloatingPanelController { origin.controller }
165172

@@ -181,12 +188,25 @@ class FloatingPanelCoordinatorProxy {
181188
mainHostingController: UIHostingController<Main>,
182189
contentHostingController: UIHostingController<Content>
183190
) {
191+
// Store the content hosting controller reference
192+
self.contentHostingController = contentHostingController
193+
184194
origin.setupFloatingPanel(
185195
mainHostingController: mainHostingController,
186196
contentHostingController: contentHostingController
187197
)
188198
}
189199

200+
/// Updates the content of the floating panel with new content.
201+
func updateContent<Content: View>(_ newContent: Content) {
202+
guard
203+
let hostingController = contentHostingController as? UIHostingController<Content>
204+
else {
205+
return
206+
}
207+
hostingController.rootView = newContent
208+
}
209+
190210
func onUpdate<Representable>(
191211
context: UIViewControllerRepresentableContext<Representable>
192212
) where Representable: UIViewControllerRepresentable {

0 commit comments

Comments
 (0)