From b53b53e9b00131f0df5f8ba0b2a7cb1567bd9609 Mon Sep 17 00:00:00 2001 From: Shaps Benkau Date: Mon, 30 Jan 2023 23:07:52 +0000 Subject: [PATCH] Introduces rudimentary Toolbar support for iOS 13 --- .../PhotosPicker/UI/PhotosPickerView.swift | 80 +++++-------- .../Shared/Toolbar/Toolbar.swift | 106 +++++++++++++++--- 2 files changed, 123 insertions(+), 63 deletions(-) diff --git a/Sources/SwiftUIBackports/Shared/PhotosPicker/UI/PhotosPickerView.swift b/Sources/SwiftUIBackports/Shared/PhotosPicker/UI/PhotosPickerView.swift index e139407..a5314be 100644 --- a/Sources/SwiftUIBackports/Shared/PhotosPicker/UI/PhotosPickerView.swift +++ b/Sources/SwiftUIBackports/Shared/PhotosPicker/UI/PhotosPickerView.swift @@ -28,66 +28,48 @@ internal struct PhotosPickerView: View { .navigationBarTitle(Text("Photos"), displayMode: .inline) .backport.toolbar { Backport.ToolbarItem(placement: .primaryAction) { - Text("Done") + Button("Add") { + + } + .font(.body.weight(.semibold)) + .disabled(selection.isEmpty) + .opacity(maxSelection == 1 ? 0 : 1) } Backport.ToolbarItem(placement: .cancellationAction) { - Text("Cancel") + Button("Cancel") { + selection = [] + dismiss() + } } 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() - } - - private var picker: some View { - Picker("", selection: $source) { - ForEach(Source.allCases) { source in - Text(source.rawValue) - .tag(source) - } + .backport.onChange(of: source) { newValue in + selection = source == .albums ? [.init(itemIdentifier: "")] : [] } - .pickerStyle(.segmented) - .fixedSize() } } - -//private extension Backport where Wrapped: View { -// @ViewBuilder -// func toolbar(@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 diff --git a/Sources/SwiftUIBackports/Shared/Toolbar/Toolbar.swift b/Sources/SwiftUIBackports/Shared/Toolbar/Toolbar.swift index 0a9ac59..953612d 100644 --- a/Sources/SwiftUIBackports/Shared/Toolbar/Toolbar.swift +++ b/Sources/SwiftUIBackports/Shared/Toolbar/Toolbar.swift @@ -9,6 +9,7 @@ public extension Backport { case cancellationAction case destructiveAction case principal + case status var isLeading: Bool { switch self { @@ -30,16 +31,18 @@ public extension Backport { } struct ToolbarItem: View { + let id: String let placement: Backport.ToolbarItemPlacement let content: AnyView - public init(placement: Backport.ToolbarItemPlacement, @ViewBuilder content: () -> Content) { + public init(id: String = UUID().uuidString, placement: Backport.ToolbarItemPlacement = .automatic, @ViewBuilder content: () -> Content) { + self.id = id self.placement = placement self.content = AnyView(content()) } public var body: some View { - content + content.id(id) } } } @@ -57,26 +60,101 @@ extension Collection where Element == Backport.ToolbarItem, Indices: Random } @available(iOS, introduced: 13, deprecated: 14) -public extension Backport where Wrapped: View { +struct ToolbarModifier: ViewModifier { + let leadingItems: [Backport.ToolbarItem] + let trailingItems: [Backport.ToolbarItem] + let principalItems: [Backport.ToolbarItem] + let statusItems: [Backport.ToolbarItem] + + init(items: [Backport.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 - func toolbar(@BackportToolbarContentBuilder _ items: () -> [Backport.ToolbarItem]) -> some View { - let items = items() + private var leading: some View { + 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 - .navigationBarItems(leading: items.filter { $0.placement.isLeading }.content, trailing: items.filter { $0.placement.isTrailing }.content) + .navigationBarItems(leading: leading, trailing: trailing) .controller { controller in - controller?.navigationItem.titleView = UIHostingController( - rootView: items.filter { $0.placement == .principal }.content, - ignoreSafeArea: false - ).view + if !principalItems.isEmpty { + controller?.navigationItem.titleView = UIHostingController( + rootView: principal, + ignoreSafeArea: false + ).view + } + + if !statusItems.isEmpty { + controller?.navigationController?.setToolbarHidden(false, animated: false) + controller?.toolbarItems = [ + + ] + } } } } -@resultBuilder -public struct BackportToolbarContentBuilder { } -public extension BackportToolbarContentBuilder { +@available(iOS, introduced: 13, deprecated: 14) +public extension Backport where Wrapped: View { + @ViewBuilder + func toolbar(@Backport.ToolbarContentBuilder _ items: () -> [Backport.ToolbarItem]) -> some View { + content.modifier(ToolbarModifier(items: items())) + } +} + +public extension Backport { + @resultBuilder struct ToolbarContentBuilder { } +} + +public extension Backport.ToolbarContentBuilder { static func buildBlock() -> [Backport.ToolbarItem] { - [Backport.ToolbarItem(placement: .automatic, content: { EmptyView() })] + [.init(content: EmptyView.init)] } static func buildBlock(_ content: Backport.ToolbarItem) -> [Backport.ToolbarItem] {