Adds support for limited toolbar placements
This commit is contained in:
parent
b53b53e9b0
commit
70abdd08b7
|
@ -1,6 +1,7 @@
|
|||
import SwiftUI
|
||||
|
||||
public final class ImageRenderer<Content>: ObservableObject where Content: View {
|
||||
public extension Backport<Any> {
|
||||
final class ImageRenderer<Content>: ObservableObject where Content: View {
|
||||
public var content: Content
|
||||
public var label: String?
|
||||
public var proposedSize: ProposedViewSize = .unspecified
|
||||
|
@ -11,15 +12,10 @@ public final class ImageRenderer<Content>: ObservableObject where Content: View
|
|||
public init(content: Content) {
|
||||
self.content = content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//public extension ImageRenderer {
|
||||
// func render(rasterizationScale: CGFloat = 1, renderer: (CGSize, (CGContext) -> Void) -> Void) {
|
||||
//
|
||||
// }
|
||||
//}
|
||||
|
||||
public extension ImageRenderer {
|
||||
public extension Backport<Any>.ImageRenderer {
|
||||
var cgImage: CGImage? {
|
||||
#if os(macOS)
|
||||
nsImage?.cgImage(forProposedRect: nil, context: .current, hints: nil)
|
||||
|
|
|
@ -54,7 +54,7 @@ internal struct PhotosPickerView: View {
|
|||
.fixedSize()
|
||||
}
|
||||
|
||||
Backport.ToolbarItem(placement: .status) {
|
||||
Backport.ToolbarItem(placement: .bottomBar) {
|
||||
VStack {
|
||||
Text(selection.isEmpty ? "Select Items" : "Selected (\(selection.count))")
|
||||
.font(.subheadline.weight(.semibold))
|
||||
|
|
|
@ -44,7 +44,7 @@ extension Image: Shareable {
|
|||
let url = URL(fileURLWithPath: NSTemporaryDirectory())
|
||||
.appendingPathComponent("\(UUID().uuidString)")
|
||||
.appendingPathExtension(pathExtension)
|
||||
let renderer = ImageRenderer(content: self)
|
||||
let renderer = Backport.ImageRenderer(content: self)
|
||||
|
||||
#if os(iOS)
|
||||
let data = renderer.uiImage?.jpegData(compressionQuality: 0.8)
|
||||
|
|
|
@ -9,7 +9,7 @@ public extension Backport<Any> {
|
|||
case cancellationAction
|
||||
case destructiveAction
|
||||
case principal
|
||||
case status
|
||||
case bottomBar
|
||||
|
||||
var isLeading: Bool {
|
||||
switch self {
|
||||
|
@ -64,13 +64,13 @@ struct ToolbarModifier: ViewModifier {
|
|||
let leadingItems: [Backport<Any>.ToolbarItem]
|
||||
let trailingItems: [Backport<Any>.ToolbarItem]
|
||||
let principalItems: [Backport<Any>.ToolbarItem]
|
||||
let statusItems: [Backport<Any>.ToolbarItem]
|
||||
let bottomBarItems: [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 }
|
||||
bottomBarItems = items.filter { $0.placement == .bottomBar }
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
@ -107,10 +107,10 @@ struct ToolbarModifier: ViewModifier {
|
|||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var status: some View {
|
||||
if !statusItems.isEmpty {
|
||||
private var bottomBar: some View {
|
||||
if !bottomBarItems.isEmpty {
|
||||
HStack {
|
||||
ForEach(statusItems, id: \.id) { item in
|
||||
ForEach(bottomBarItems, id: \.id) { item in
|
||||
item.content
|
||||
}
|
||||
}
|
||||
|
@ -125,18 +125,35 @@ struct ToolbarModifier: ViewModifier {
|
|||
.controller { controller in
|
||||
if !principalItems.isEmpty {
|
||||
controller?.navigationItem.titleView = UIHostingController(
|
||||
rootView: principal,
|
||||
rootView: principal.backport.largeScale(),
|
||||
ignoreSafeArea: false
|
||||
).view
|
||||
controller?.navigationItem.titleView?.backgroundColor = .clear
|
||||
}
|
||||
|
||||
if !statusItems.isEmpty {
|
||||
if !bottomBarItems.isEmpty {
|
||||
controller?.navigationController?.setToolbarHidden(false, animated: false)
|
||||
controller?.toolbarItems = [
|
||||
controller?.toolbarItems = bottomBarItems.map {
|
||||
let view = UIHostingController(rootView: $0.content.backport.largeScale()).view!
|
||||
view.backgroundColor = .clear
|
||||
return .init(customView: view)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
private extension Backport where Wrapped: View {
|
||||
func largeScale() -> some View {
|
||||
#if os(macOS)
|
||||
if #available(macOS 11, *) {
|
||||
content.imageScale(.large)
|
||||
} else {
|
||||
content
|
||||
}
|
||||
#else
|
||||
content.imageScale(.large)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import SwiftUI
|
||||
|
||||
struct ToolbarViews {
|
||||
var navigationBar: AnyView?
|
||||
var bottomBar: AnyView?
|
||||
var tabBar: AnyView?
|
||||
}
|
||||
|
||||
struct ToolbarVisibility {
|
||||
var navigationBar: Backport<Any>.Visibility?
|
||||
var bottomBar: Backport<Any>.Visibility?
|
||||
var tabBar: Backport<Any>.Visibility?
|
||||
}
|
||||
|
||||
private struct ToolbarViewsKey: EnvironmentKey {
|
||||
static var defaultValue: ToolbarViews = .init()
|
||||
}
|
||||
|
||||
private struct ToolbarVisibilityKey: EnvironmentKey {
|
||||
static var defaultValue: ToolbarVisibility = .init()
|
||||
}
|
||||
|
||||
internal extension EnvironmentValues {
|
||||
var toolbarViews: ToolbarViews {
|
||||
get { self[ToolbarViewsKey.self] }
|
||||
set { self[ToolbarViewsKey.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
internal extension EnvironmentValues {
|
||||
var toolbarVisibility: ToolbarVisibility {
|
||||
get { self[ToolbarVisibilityKey.self] }
|
||||
set { self[ToolbarVisibilityKey.self] = newValue }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
import SwiftUI
|
||||
|
||||
public extension Backport<Any> {
|
||||
struct ToolbarPlacement: Hashable {
|
||||
enum Placement: Hashable {
|
||||
var id: Self { self }
|
||||
case bottomBar
|
||||
case navigationbar
|
||||
case tabBar
|
||||
}
|
||||
|
||||
let placement: Placement
|
||||
|
||||
/// The bottom toolbar of an app.
|
||||
@available(macOS, unavailable)
|
||||
public static var bottomBar: ToolbarPlacement {
|
||||
.init(placement: .bottomBar)
|
||||
}
|
||||
|
||||
/// The navigation bar of an app.
|
||||
@available(macOS, unavailable)
|
||||
public static var navigationBar: ToolbarPlacement {
|
||||
.init(placement: .navigationbar)
|
||||
}
|
||||
|
||||
/// The tab bar of an app.
|
||||
@available(macOS, unavailable)
|
||||
public static var tabBar: ToolbarPlacement {
|
||||
.init(placement: .tabBar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//@available(iOS, deprecated: 16)
|
||||
//@available(macOS, deprecated: 13)
|
||||
//@available(tvOS, unavailable)
|
||||
//@available(watchOS, unavailable)
|
||||
//public extension Backport where Wrapped: View {
|
||||
// func toolbarBackground(_ visibility: Backport<Any>.Visibility, for bars: Backport<Any>.ToolbarPlacement...) -> some View {
|
||||
// content
|
||||
// .modifier(ToolbarBackgroundModifier())
|
||||
// .environment(\.toolbarVisibility, .init(
|
||||
// navigationBar: bars.contains(.navigationBar) ? visibility : nil,
|
||||
// bottomBar: bars.contains(.bottomBar) ? visibility : nil,
|
||||
// tabBar: bars.contains(.tabBar) ? visibility : nil)
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func toolbarBackground<S>(_ style: S, for bars: Backport<Any>.ToolbarPlacement...) -> some View where S: ShapeStyle & View {
|
||||
// content
|
||||
// .modifier(ToolbarBackgroundModifier())
|
||||
// .environment(\.toolbarViews, .init(
|
||||
// navigationBar: bars.contains(.navigationBar) ? .init(style) : nil,
|
||||
// bottomBar: bars.contains(.bottomBar) ? .init(style) : nil,
|
||||
// tabBar: bars.contains(.tabBar) ? .init(style) : nil)
|
||||
// )
|
||||
// }
|
||||
//}
|
|
@ -0,0 +1,93 @@
|
|||
import SwiftUI
|
||||
|
||||
internal struct ToolbarBackgroundModifier: ViewModifier {
|
||||
@Environment(\.toolbarViews) private var toolbarViews
|
||||
@Environment(\.toolbarVisibility) private var toolbarVisibility
|
||||
|
||||
@Backport.StateObject private var wrapper: ControllerWrapper = .init()
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.controller { controller in
|
||||
wrapper.controller = controller
|
||||
}
|
||||
.backport.task {
|
||||
updateNavigationBar()
|
||||
updateBottomBar()
|
||||
updateTabBar()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateNavigationBar() {
|
||||
guard toolbarVisibility.navigationBar != nil || toolbarViews.navigationBar != nil else { return }
|
||||
guard let bar = wrapper.controller?.navigationController?.navigationBar else { return }
|
||||
let appearance = UINavigationBarAppearance()
|
||||
|
||||
if let visibilty = toolbarVisibility.navigationBar, visibilty == .hidden {
|
||||
appearance.configureWithTransparentBackground()
|
||||
} else if let view = toolbarViews.navigationBar {
|
||||
appearance.backgroundImage = Backport.ImageRenderer(
|
||||
content: view
|
||||
.frame(width: bar.bounds.width, height: bar.bounds.height)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
).uiImage
|
||||
} else {
|
||||
appearance.configureWithDefaultBackground()
|
||||
}
|
||||
|
||||
bar.standardAppearance = appearance
|
||||
bar.compactAppearance = appearance
|
||||
bar.scrollEdgeAppearance = appearance
|
||||
if #available(iOS 15.0, *) {
|
||||
bar.compactScrollEdgeAppearance = appearance
|
||||
}
|
||||
}
|
||||
|
||||
private func updateBottomBar() {
|
||||
guard toolbarVisibility.bottomBar != nil || toolbarViews.bottomBar != nil else { return }
|
||||
guard let bar = wrapper.controller?.navigationController?.toolbar else { return }
|
||||
let appearance = UIToolbarAppearance()
|
||||
|
||||
if let visibilty = toolbarVisibility.bottomBar, visibilty == .hidden {
|
||||
appearance.configureWithTransparentBackground()
|
||||
} else {
|
||||
appearance.configureWithDefaultBackground()
|
||||
}
|
||||
|
||||
bar.standardAppearance = appearance
|
||||
bar.compactAppearance = appearance
|
||||
if #available(iOS 15.0, *) {
|
||||
bar.scrollEdgeAppearance = appearance
|
||||
}
|
||||
if #available(iOS 15.0, *) {
|
||||
bar.compactScrollEdgeAppearance = appearance
|
||||
}
|
||||
}
|
||||
|
||||
private func updateTabBar() {
|
||||
guard toolbarVisibility.tabBar != nil || toolbarViews.tabBar != nil else { return }
|
||||
guard let bar = wrapper.controller?.tabBarController?.tabBar else { return }
|
||||
let appearance = UITabBarAppearance()
|
||||
|
||||
if let visibilty = toolbarVisibility.tabBar, visibilty == .hidden {
|
||||
appearance.configureWithTransparentBackground()
|
||||
} else if let view = toolbarViews.tabBar {
|
||||
appearance.backgroundImage = Backport.ImageRenderer(
|
||||
content: view
|
||||
.frame(width: bar.bounds.width, height: bar.bounds.height)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
).uiImage
|
||||
} else {
|
||||
appearance.configureWithDefaultBackground()
|
||||
}
|
||||
|
||||
bar.standardAppearance = appearance
|
||||
if #available(iOS 15.0, *) {
|
||||
bar.scrollEdgeAppearance = appearance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class ControllerWrapper: ObservableObject {
|
||||
weak var controller: UIViewController?
|
||||
}
|
Loading…
Reference in New Issue