Introduces rudimentary Toolbar support for iOS 13

This commit is contained in:
Shaps Benkau 2023-01-30 23:07:52 +00:00
parent cce0b59d3d
commit b53b53e9b0
2 changed files with 123 additions and 63 deletions

View File

@ -28,66 +28,48 @@ internal struct PhotosPickerView: View {
.navigationBarTitle(Text("Photos"), displayMode: .inline) .navigationBarTitle(Text("Photos"), displayMode: .inline)
.backport.toolbar { .backport.toolbar {
Backport.ToolbarItem(placement: .primaryAction) { Backport.ToolbarItem(placement: .primaryAction) {
Text("Done") Button("Add") {
}
.font(.body.weight(.semibold))
.disabled(selection.isEmpty)
.opacity(maxSelection == 1 ? 0 : 1)
} }
Backport.ToolbarItem(placement: .cancellationAction) { Backport.ToolbarItem(placement: .cancellationAction) {
Text("Cancel") Button("Cancel") {
selection = []
dismiss()
}
} }
Backport.ToolbarItem(placement: .principal) { Backport.ToolbarItem(placement: .principal) {
Text("Principal") Picker("", selection: $source) {
ForEach(Source.allCases) { source in
Text(source.rawValue)
.tag(source)
}
}
.pickerStyle(.segmented)
.fixedSize()
}
Backport.ToolbarItem(placement: .status) {
VStack {
Text(selection.isEmpty ? "Select Items" : "Selected (\(selection.count))")
.font(.subheadline.weight(.semibold))
Text("Select up to \(maxSelection ?? 1) items.")
.foregroundColor(.secondary)
.font(.footnote)
}
} }
} }
// .backport.toolbar {
// picker
// } leading: {
// Button("Cancel") {
// dismiss()
// }
// } trailing: {
// Button("Add") {
//
// }
// .font(.body.weight(.semibold))
// .disabled(selection.isEmpty)
// .opacity(maxSelection == 1 ? 0 : 1)
// }
// .controller { controller in
// if #available(iOS 14, *) { } else {
// guard controller?.navigationItem.titleView == nil else { return }
// controller?.navigationItem.titleView = UIHostingController(rootView: picker, ignoreSafeArea: false).view
// }
// }
} }
.backport.interactiveDismissDisabled() .backport.interactiveDismissDisabled()
} .backport.onChange(of: source) { newValue in
selection = source == .albums ? [.init(itemIdentifier: "")] : []
private var picker: some View {
Picker("", selection: $source) {
ForEach(Source.allCases) { source in
Text(source.rawValue)
.tag(source)
}
} }
.pickerStyle(.segmented)
.fixedSize()
} }
} }
//private extension Backport where Wrapped: View {
// @ViewBuilder
// func toolbar<Leading: View, Trailing: View, Principal: View>(@ViewBuilder principal: () -> Principal, @ViewBuilder leading: () -> Leading, @ViewBuilder trailing: () -> Trailing) -> some View {
// if #available(iOS 14, *) {
// content.toolbar {
//// ToolbarItem(placement: .navigationBarLeading, content: leading)
//// ToolbarItem(placement: .navigationBarTrailing, content: trailing)
//// ToolbarItem(placement: .principal, content: principal)
// }
// } else {
// content
// .navigationBarItems(leading: leading(), trailing: trailing())
// }
// }
//}
#endif #endif

View File

@ -9,6 +9,7 @@ public extension Backport<Any> {
case cancellationAction case cancellationAction
case destructiveAction case destructiveAction
case principal case principal
case status
var isLeading: Bool { var isLeading: Bool {
switch self { switch self {
@ -30,16 +31,18 @@ public extension Backport<Any> {
} }
struct ToolbarItem: View { struct ToolbarItem: View {
let id: String
let placement: Backport.ToolbarItemPlacement let placement: Backport.ToolbarItemPlacement
let content: AnyView let content: AnyView
public init<Content: View>(placement: Backport.ToolbarItemPlacement, @ViewBuilder content: () -> Content) { public init<Content: View>(id: String = UUID().uuidString, placement: Backport.ToolbarItemPlacement = .automatic, @ViewBuilder content: () -> Content) {
self.id = id
self.placement = placement self.placement = placement
self.content = AnyView(content()) self.content = AnyView(content())
} }
public var body: some View { public var body: some View {
content content.id(id)
} }
} }
} }
@ -57,26 +60,101 @@ extension Collection where Element == Backport<Any>.ToolbarItem, Indices: Random
} }
@available(iOS, introduced: 13, deprecated: 14) @available(iOS, introduced: 13, deprecated: 14)
public extension Backport where Wrapped: View { struct ToolbarModifier: ViewModifier {
let leadingItems: [Backport<Any>.ToolbarItem]
let trailingItems: [Backport<Any>.ToolbarItem]
let principalItems: [Backport<Any>.ToolbarItem]
let statusItems: [Backport<Any>.ToolbarItem]
init(items: [Backport<Any>.ToolbarItem]) {
leadingItems = items.filter { $0.placement.isLeading }
trailingItems = items.filter { $0.placement.isTrailing }
principalItems = items.filter { $0.placement == .principal }
statusItems = items.filter { $0.placement == .status }
}
@ViewBuilder @ViewBuilder
func toolbar(@BackportToolbarContentBuilder _ items: () -> [Backport<Any>.ToolbarItem]) -> some View { private var leading: some View {
let items = items() if !leadingItems.isEmpty {
HStack {
ForEach(leadingItems, id: \.id) { item in
item.content
}
}
}
}
@ViewBuilder
private var trailing: some View {
if !trailingItems.isEmpty {
HStack {
ForEach(trailingItems, id: \.id) { item in
item.content
}
}
}
}
@ViewBuilder
private var principal: some View {
if !principalItems.isEmpty {
HStack {
ForEach(principalItems, id: \.id) { item in
item.content
}
}
}
}
@ViewBuilder
private var status: some View {
if !statusItems.isEmpty {
HStack {
ForEach(statusItems, id: \.id) { item in
item.content
}
}
}
}
@Namespace private var namespace
func body(content: Content) -> some View {
content content
.navigationBarItems(leading: items.filter { $0.placement.isLeading }.content, trailing: items.filter { $0.placement.isTrailing }.content) .navigationBarItems(leading: leading, trailing: trailing)
.controller { controller in .controller { controller in
controller?.navigationItem.titleView = UIHostingController( if !principalItems.isEmpty {
rootView: items.filter { $0.placement == .principal }.content, controller?.navigationItem.titleView = UIHostingController(
ignoreSafeArea: false rootView: principal,
).view ignoreSafeArea: false
).view
}
if !statusItems.isEmpty {
controller?.navigationController?.setToolbarHidden(false, animated: false)
controller?.toolbarItems = [
]
}
} }
} }
} }
@resultBuilder @available(iOS, introduced: 13, deprecated: 14)
public struct BackportToolbarContentBuilder { } public extension Backport where Wrapped: View {
public extension BackportToolbarContentBuilder { @ViewBuilder
func toolbar(@Backport<Any>.ToolbarContentBuilder _ items: () -> [Backport<Any>.ToolbarItem]) -> some View {
content.modifier(ToolbarModifier(items: items()))
}
}
public extension Backport<Any> {
@resultBuilder struct ToolbarContentBuilder { }
}
public extension Backport<Any>.ToolbarContentBuilder {
static func buildBlock() -> [Backport<Any>.ToolbarItem] { static func buildBlock() -> [Backport<Any>.ToolbarItem] {
[Backport<Any>.ToolbarItem(placement: .automatic, content: { EmptyView() })] [.init(content: EmptyView.init)]
} }
static func buildBlock(_ content: Backport<Any>.ToolbarItem) -> [Backport<Any>.ToolbarItem] { static func buildBlock(_ content: Backport<Any>.ToolbarItem) -> [Backport<Any>.ToolbarItem] {