Skip to content

Commit 008784d

Browse files
committed
Improve presentation detents backport
1 parent 8e92217 commit 008784d

File tree

7 files changed

+181
-135
lines changed

7 files changed

+181
-135
lines changed

Package.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ let package = Package(
2323
targets: [
2424
.target(
2525
name: "SwiftUIBackports",
26-
dependencies: ["SwiftBackports"]
26+
dependencies: ["SwiftBackports", "SwiftUIBackportsC"]
27+
),
28+
.target(
29+
name: "SwiftUIBackportsC",
30+
publicHeadersPath: "Internal"
2731
)
2832
],
2933
swiftLanguageVersions: [.v5]

Sources/SwiftUIBackports/Shared/Toolbar/ToolbarBackground.swift

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,29 @@ public extension Backport<Any> {
3434
}
3535
}
3636

37-
//@available(iOS, deprecated: 16)
38-
//@available(macOS, deprecated: 13)
39-
//@available(tvOS, unavailable)
40-
//@available(watchOS, unavailable)
41-
//public extension Backport where Wrapped: View {
42-
// func toolbarBackground(_ visibility: Backport<Any>.Visibility, for bars: Backport<Any>.ToolbarPlacement...) -> some View {
43-
// content
44-
// .modifier(ToolbarBackgroundModifier())
45-
// .environment(\.toolbarVisibility, .init(
46-
// navigationBar: bars.contains(.navigationBar) ? visibility : nil,
47-
// bottomBar: bars.contains(.bottomBar) ? visibility : nil,
48-
// tabBar: bars.contains(.tabBar) ? visibility : nil)
49-
// )
50-
// }
51-
//
52-
// func toolbarBackground<S>(_ style: S, for bars: Backport<Any>.ToolbarPlacement...) -> some View where S: ShapeStyle & View {
53-
// content
54-
// .modifier(ToolbarBackgroundModifier())
55-
// .environment(\.toolbarViews, .init(
56-
// navigationBar: bars.contains(.navigationBar) ? .init(style) : nil,
57-
// bottomBar: bars.contains(.bottomBar) ? .init(style) : nil,
58-
// tabBar: bars.contains(.tabBar) ? .init(style) : nil)
59-
// )
60-
// }
61-
//}
37+
@available(iOS, deprecated: 16)
38+
@available(macOS, deprecated: 13)
39+
@available(tvOS, unavailable)
40+
@available(watchOS, unavailable)
41+
public extension Backport where Wrapped: View {
42+
func toolbarBackground(_ visibility: Backport<Any>.Visibility, for bars: Backport<Any>.ToolbarPlacement...) -> some View {
43+
wrapped
44+
.modifier(ToolbarBackgroundModifier())
45+
.environment(\.toolbarVisibility, .init(
46+
navigationBar: bars.contains(.navigationBar) ? visibility : nil,
47+
bottomBar: bars.contains(.bottomBar) ? visibility : nil,
48+
tabBar: bars.contains(.tabBar) ? visibility : nil)
49+
)
50+
}
51+
52+
func toolbarBackground<S>(_ style: S, for bars: Backport<Any>.ToolbarPlacement...) -> some View where S: ShapeStyle & View {
53+
wrapped
54+
.modifier(ToolbarBackgroundModifier())
55+
.environment(\.toolbarViews, .init(
56+
navigationBar: bars.contains(.navigationBar) ? .init(style) : nil,
57+
bottomBar: bars.contains(.bottomBar) ? .init(style) : nil,
58+
tabBar: bars.contains(.tabBar) ? .init(style) : nil)
59+
)
60+
}
61+
}
6262
#endif

Sources/SwiftUIBackports/iOS/Presentation/BackgroundInteraction.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ private extension Backport.Representable {
152152
controller.largestUndimmedDetentIdentifier = .init(detent.id.rawValue)
153153

154154
let selectedId = controller.selectedDetentIdentifier ?? .large
155-
let selected = Backport<Any>.PresentationDetent(id: .init(rawValue: selectedId.rawValue))
155+
let selected = Backport<Any>.PresentationDetent(id: .init(rawValue: selectedId.rawValue))!
156156
controller.presentingViewController.view?.tintAdjustmentMode = selected > detent ? .dimmed : .normal
157157
}
158158
}

Sources/SwiftUIBackports/iOS/Presentation/Detents.swift

Lines changed: 77 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import SwiftUI
22
import SwiftBackports
3+
import SwiftUIBackportsC
34

45
@available(tvOS, deprecated: 16)
56
@available(macOS, deprecated: 13)
@@ -30,7 +31,7 @@ public extension Backport where Wrapped: View {
3031
@available(iOS, introduced: 15, deprecated: 16, message: "Presentation detents are only supported in iOS 15+")
3132
func presentationDetents(_ detents: Set<Backport<Any>.PresentationDetent>) -> some View {
3233
#if os(iOS)
33-
wrapped.background(Backport<Any>.Representable(detents: detents, selection: nil))
34+
wrapped.background(Backport<Any>.Representable(detents: detents, largestUndimmed: nil, selection: nil))
3435
#else
3536
wrapped
3637
#endif
@@ -69,9 +70,9 @@ public extension Backport where Wrapped: View {
6970
/// provide for the `detents` parameter.
7071
@ViewBuilder
7172
@available(iOS, introduced: 15, deprecated: 16, message: "Presentation detents are only supported in iOS 15+")
72-
func presentationDetents(_ detents: Set<Backport<Any>.PresentationDetent>, selection: Binding<Backport<Any>.PresentationDetent>) -> some View {
73+
func presentationDetents(_ detents: Set<Backport<Any>.PresentationDetent>, largestUndimmed: Backport<Any>.PresentationDetent? = nil, selection: Binding<Backport<Any>.PresentationDetent>) -> some View {
7374
#if os(iOS)
74-
wrapped.background(Backport<Any>.Representable(detents: detents, selection: selection))
75+
wrapped.background(Backport<Any>.Representable(detents: detents, largestUndimmed: largestUndimmed, selection: selection))
7576
#else
7677
wrapped
7778
#endif
@@ -85,47 +86,70 @@ public extension Backport where Wrapped: View {
8586
public extension Backport<Any> {
8687

8788
/// A type that represents a height where a sheet naturally rests.
88-
struct PresentationDetent: Hashable, Comparable {
89+
enum PresentationDetent: Hashable, Comparable {
8990

90-
public struct Identifier: RawRepresentable, Hashable {
91-
public var rawValue: String
92-
public init(rawValue: String) {
93-
self.rawValue = rawValue
94-
}
91+
case medium, large, height(_ constant: CGFloat)
9592

96-
public static var medium: Identifier {
97-
.init(rawValue: "com.apple.UIKit.medium")
98-
}
93+
@available(iOS 15.0, *)
94+
init?(id: UISheetPresentationController.Detent.Identifier) {
95+
switch id {
96+
case .medium:
97+
self = .medium
98+
99+
case .large:
100+
self = .large
99101

100-
public static var large: Identifier {
101-
.init(rawValue: "com.apple.UIKit.large")
102+
default:
103+
if let number = NumberFormatter().number(from: id.rawValue) {
104+
let value = CGFloat(truncating: number)
105+
self = .height(value)
106+
} else {
107+
return nil
108+
}
109+
}
102110
}
103-
}
104111

105-
public let id: Identifier
112+
@available(iOS 15.0, *)
113+
public var id: UISheetPresentationController.Detent.Identifier {
114+
switch self {
115+
case .medium:
116+
return .medium
106117

107-
/// The system detent for a sheet that's approximately half the height of
108-
/// the screen, and is inactive in compact height.
109-
public static var medium: PresentationDetent {
110-
.init(id: .medium)
111-
}
118+
case .large:
119+
return .large
112120

113-
/// The system detent for a sheet at full height.
114-
public static var large: PresentationDetent {
115-
.init(id: .large)
121+
case let .height(value):
122+
return .init(value.description)
123+
}
116124
}
117125

118-
fileprivate static var none: PresentationDetent {
119-
return .init(id: .init(rawValue: ""))
126+
@available(iOS 15.0, *)
127+
var system: UISheetPresentationController.Detent {
128+
switch self {
129+
case .medium:
130+
return .medium()
131+
132+
case .large:
133+
return .large()
134+
135+
case let .height(constant):
136+
if #available(iOS 16.0, *) {
137+
return .custom(identifier: id, resolver: {_ in constant})
138+
} else {
139+
return ._detent(withIdentifier: id.rawValue, constant: constant)
140+
}
141+
}
120142
}
121143

122144
public static func < (lhs: PresentationDetent, rhs: PresentationDetent) -> Bool {
123-
switch (lhs, rhs) {
124-
case (.large, .medium):
125-
return false
126-
default:
127-
return true
145+
func approxHeight(_ detent: PresentationDetent) -> CGFloat {
146+
switch detent {
147+
case .medium: return UIScreen.main.bounds.height * 0.5
148+
case .large: return UIScreen.main.bounds.height
149+
case .height(let height): return height
150+
}
128151
}
152+
return approxHeight(lhs) < approxHeight(rhs)
129153
}
130154
}
131155
}
@@ -135,14 +159,15 @@ public extension Backport<Any> {
135159
private extension Backport<Any> {
136160
struct Representable: UIViewControllerRepresentable {
137161
let detents: Set<Backport<Any>.PresentationDetent>
162+
let largestUndimmed: Backport<Any>.PresentationDetent?
138163
let selection: Binding<Backport<Any>.PresentationDetent>?
139164

140165
func makeUIViewController(context: Context) -> Backport.Representable.Controller {
141-
Controller(detents: detents, selection: selection)
166+
Controller(detents: detents, largestUndimmed: largestUndimmed, selection: selection)
142167
}
143168

144169
func updateUIViewController(_ controller: Backport.Representable.Controller, context: Context) {
145-
controller.update(detents: detents, selection: selection)
170+
controller.update(detents: detents, largestUndimmed: largestUndimmed, selection: selection)
146171
}
147172
}
148173
}
@@ -156,9 +181,10 @@ private extension Backport.Representable {
156181
var largestUndimmed: Backport<Any>.PresentationDetent?
157182
weak var _delegate: UISheetPresentationControllerDelegate?
158183

159-
init(detents: Set<Backport<Any>.PresentationDetent>, selection: Binding<Backport<Any>.PresentationDetent>?) {
184+
init(detents: Set<Backport<Any>.PresentationDetent>, largestUndimmed: Backport<Any>.PresentationDetent? = nil, selection: Binding<Backport<Any>.PresentationDetent>?) {
160185
self.detents = detents
161186
self.selection = selection
187+
self.largestUndimmed = largestUndimmed
162188
super.init(nibName: nil, bundle: nil)
163189
}
164190

@@ -174,41 +200,33 @@ private extension Backport.Representable {
174200
controller.delegate = self
175201
}
176202
}
177-
update(detents: detents, selection: selection)
203+
update(detents: detents, largestUndimmed: largestUndimmed, selection: selection)
178204
}
179205

180206
override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
181207
super.willTransition(to: newCollection, with: coordinator)
182-
update(detents: detents, selection: selection)
208+
update(detents: detents, largestUndimmed: largestUndimmed, selection: selection)
183209
}
184210

185-
func update(detents: Set<Backport<Any>.PresentationDetent>, selection: Binding<Backport<Any>.PresentationDetent>?) {
211+
func update(detents: Set<Backport<Any>.PresentationDetent>, largestUndimmed: Backport<Any>.PresentationDetent?, selection: Binding<Backport<Any>.PresentationDetent>?) {
186212
self.detents = detents
187213
self.selection = selection
214+
self.largestUndimmed = largestUndimmed
188215

189216
if let controller = parent?.sheetPresentationController {
190-
controller.animateChanges {
191-
controller.detents = detents.sorted().map {
192-
switch $0 {
193-
case .medium:
194-
return .medium()
195-
default:
196-
return .large()
217+
DispatchQueue.main.async {
218+
controller.animateChanges {
219+
controller.detents = detents.sorted().map {
220+
$0.system
197221
}
198-
}
199222

200-
if let selection = selection {
201-
controller.selectedDetentIdentifier = .init(selection.wrappedValue.id.rawValue)
202-
}
223+
controller.largestUndimmedDetentIdentifier = largestUndimmed?.id
203224

204-
controller.prefersScrollingExpandsWhenScrolledToEdge = true
205-
}
225+
if let selection = selection {
226+
controller.selectedDetentIdentifier = .init(selection.wrappedValue.id.rawValue)
227+
}
206228

207-
UIView.animate(withDuration: 0.25) {
208-
if let undimmed = controller.largestUndimmedDetentIdentifier {
209-
controller.presentingViewController.view?.tintAdjustmentMode = (selection?.wrappedValue ?? .large) >= .init(id: .init(rawValue: undimmed.rawValue)) ? .automatic : .normal
210-
} else {
211-
controller.presentingViewController.view?.tintAdjustmentMode = .automatic
229+
controller.prefersScrollingExpandsWhenScrolledToEdge = true
212230
}
213231
}
214232
}
@@ -217,11 +235,11 @@ private extension Backport.Representable {
217235
func sheetPresentationControllerDidChangeSelectedDetentIdentifier(_ sheetPresentationController: UISheetPresentationController) {
218236
guard
219237
let selection = selection,
220-
let id = sheetPresentationController.selectedDetentIdentifier?.rawValue,
221-
selection.wrappedValue.id.rawValue != id
238+
let id = sheetPresentationController.selectedDetentIdentifier,
239+
let newSection = Backport<Any>.PresentationDetent(id: id),
240+
selection.wrappedValue != newSection
222241
else { return }
223-
224-
selection.wrappedValue = .init(id: .init(rawValue: id))
242+
selection.wrappedValue = newSection
225243
}
226244

227245
override func responds(to aSelector: Selector!) -> Bool {

0 commit comments

Comments
 (0)