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,42 +28,22 @@ 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")
}
}
// .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()
}
private var picker: some View {
Picker("", selection: $source) { Picker("", selection: $source) {
ForEach(Source.allCases) { source in ForEach(Source.allCases) { source in
Text(source.rawValue) Text(source.rawValue)
@ -73,21 +53,23 @@ internal struct PhotosPickerView: View {
.pickerStyle(.segmented) .pickerStyle(.segmented)
.fixedSize() .fixedSize()
} }
}
//private extension Backport where Wrapped: View { Backport.ToolbarItem(placement: .status) {
// @ViewBuilder VStack {
// func toolbar<Leading: View, Trailing: View, Principal: View>(@ViewBuilder principal: () -> Principal, @ViewBuilder leading: () -> Leading, @ViewBuilder trailing: () -> Trailing) -> some View { Text(selection.isEmpty ? "Select Items" : "Selected (\(selection.count))")
// if #available(iOS 14, *) { .font(.subheadline.weight(.semibold))
// content.toolbar {
//// ToolbarItem(placement: .navigationBarLeading, content: leading) Text("Select up to \(maxSelection ?? 1) items.")
//// ToolbarItem(placement: .navigationBarTrailing, content: trailing) .foregroundColor(.secondary)
//// ToolbarItem(placement: .principal, content: principal) .font(.footnote)
// } }
// } else { }
// content }
// .navigationBarItems(leading: leading(), trailing: trailing()) }
// } .backport.interactiveDismissDisabled()
// } .backport.onChange(of: source) { newValue in
//} selection = source == .albums ? [.init(itemIdentifier: "")] : []
}
}
}
#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 {
content HStack {
.navigationBarItems(leading: items.filter { $0.placement.isLeading }.content, trailing: items.filter { $0.placement.isTrailing }.content) ForEach(leadingItems, id: \.id) { item in
.controller { controller in item.content
controller?.navigationItem.titleView = UIHostingController( }
rootView: items.filter { $0.placement == .principal }.content,
ignoreSafeArea: false
).view
} }
} }
} }
@resultBuilder @ViewBuilder
public struct BackportToolbarContentBuilder { } private var trailing: some View {
public extension BackportToolbarContentBuilder { 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
.navigationBarItems(leading: leading, trailing: trailing)
.controller { controller in
if !principalItems.isEmpty {
controller?.navigationItem.titleView = UIHostingController(
rootView: principal,
ignoreSafeArea: false
).view
}
if !statusItems.isEmpty {
controller?.navigationController?.setToolbarHidden(false, animated: false)
controller?.toolbarItems = [
]
}
}
}
}
@available(iOS, introduced: 13, deprecated: 14)
public extension Backport where Wrapped: View {
@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] {