Skip to content

Commit c036065

Browse files
committed
Fixes a bug where inspect couldn't find some scrollviews
Fixes a bug where parentController could result in an infinite loop Disabling scrolling now works as expected Introduces preliminary ShareLink API + ActivityView API for showing the sheet Label now correctly drops the text in navigation bar contexts Various demo updates
1 parent edd8426 commit c036065

17 files changed

Lines changed: 655 additions & 66 deletions

Package.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ let package = Package(
1717
targets: ["SwiftUIBackports"]
1818
),
1919
],
20-
dependencies: [
21-
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
22-
],
2320
targets: [
2421
.target(name: "SwiftUIBackports")
2522
],

Sources/SwiftUIBackports/Extras/FittingScrollView.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import SwiftUI
22

33
/// A scrollview that behaves more similarly to a `VStack` when its content size is small enough.
44
public struct FittingScrollView<Content: View>: View {
5-
65
private let content: Content
76
private let showsIndicators: Bool
87

Sources/SwiftUIBackports/Internal/Inspect+UIKit.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ internal extension PlatformView {
4343
views.remove(at: index)
4444

4545
for subview in views.reversed() {
46-
if let typed = subview.child(ofType: type) {
46+
if let typed = subview as? ViewType {
47+
return typed
48+
} else if let typed = subview.child(ofType: type) {
4749
return typed
4850
}
4951
}
@@ -76,6 +78,10 @@ internal struct Inspector {
7678
func sibling<ViewType: PlatformView>(ofType: ViewType.Type) -> ViewType? {
7779
hostView.sibling(ofType: ViewType.self)
7880
}
81+
82+
func child<ViewType: PlatformView>(ofType: ViewType.Type) -> ViewType? {
83+
hostView.child(ofType: ViewType.self)
84+
}
7985
}
8086

8187
extension View {

Sources/SwiftUIBackports/Internal/OwningController.swift

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@ import UIKit
44
public extension UIView {
55

66
var parentController: UIViewController? {
7-
var responder: UIResponder? = self
8-
9-
while !(responder is UIViewController) && superview != nil {
10-
if let next = responder?.next {
11-
responder = next
12-
}
7+
if let responder = self.next as? UIViewController {
8+
return responder
9+
} else if let responder = self.next as? UIView {
10+
return responder.parentController
11+
} else {
12+
return nil
1313
}
14-
15-
return responder as? UIViewController
1614
}
1715

1816
}
@@ -24,15 +22,13 @@ import AppKit
2422
public extension NSView {
2523

2624
var parentController: NSViewController? {
27-
var responder: NSResponder? = self
28-
29-
while !(responder is NSViewController) && superview != nil {
30-
if let next = responder?.nextResponder {
31-
responder = next
32-
}
25+
if let responder = self.next as? NSViewController {
26+
return responder
27+
} else if let responder = self.next as? NSView {
28+
return responder.parentController
29+
} else {
30+
return nil
3331
}
34-
35-
return responder as? NSViewController
3632
}
3733

3834
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import SwiftUI
2+
3+
public struct ProposedViewSize: Equatable, Sendable {
4+
public var width: CGFloat?
5+
public var height: CGFloat?
6+
7+
public static let zero = Self(width: 0, height: 0)
8+
public static let infinity = Self(width: .infinity, height: .infinity)
9+
public static let unspecified = Self(width: nil, height: nil)
10+
11+
public init(_ size: CGSize) {
12+
self.width = size.width
13+
self.height = size.height
14+
}
15+
16+
public init(width: CGFloat?, height: CGFloat?) {
17+
self.width = width
18+
self.height = height
19+
}
20+
21+
public func replacingUnspecifiedDimensions(by size: CGSize) -> CGSize {
22+
.init(
23+
width: width ?? size.width,
24+
height: height ?? size.height
25+
)
26+
}
27+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import SwiftUI
2+
3+
public final class ImageRenderer<Content>: ObservableObject where Content: View {
4+
public var content: Content
5+
public var label: String?
6+
public var proposedSize: ProposedViewSize = .unspecified
7+
public var scale: CGFloat = UIScreen.main.scale
8+
public var isOpaque: Bool = false
9+
public var colorMode: ColorRenderingMode = .nonLinear
10+
11+
public init(content: Content) {
12+
self.content = content
13+
}
14+
}
15+
16+
//public extension ImageRenderer {
17+
// func render(rasterizationScale: CGFloat = 1, renderer: (CGSize, (CGContext) -> Void) -> Void) {
18+
//
19+
// }
20+
//}
21+
22+
public extension ImageRenderer {
23+
var cgImage: CGImage? {
24+
#if os(macOS)
25+
nsImage?.cgImage
26+
#else
27+
uiImage?.cgImage
28+
#endif
29+
}
30+
31+
#if os(macOS)
32+
33+
var nsImage: NSImage? {
34+
nil
35+
}
36+
37+
#else
38+
39+
var uiImage: UIImage? {
40+
let controller = UIHostingController(rootView: content)
41+
let size = controller.view.intrinsicContentSize
42+
controller.view.bounds = CGRect(origin: .zero, size: size)
43+
controller.view.backgroundColor = .clear
44+
45+
let format = UIGraphicsImageRendererFormat(for: controller.traitCollection)
46+
format.opaque = isOpaque
47+
format.preferredRange = colorMode.range
48+
format.scale = scale
49+
50+
let renderer = UIGraphicsImageRenderer(size: size, format: format)
51+
52+
let image = renderer.image { context in
53+
controller.view.drawHierarchy(in: context.format.bounds, afterScreenUpdates: true)
54+
}
55+
56+
image.accessibilityLabel = label
57+
objectWillChange.send()
58+
59+
return image
60+
}
61+
62+
#endif
63+
}
64+
65+
#if os(macOS)
66+
#else
67+
extension ColorRenderingMode {
68+
var range: UIGraphicsImageRendererFormat.Range {
69+
switch self {
70+
case .extendedLinear: return .extended
71+
case .linear: return .standard
72+
default: return .automatic
73+
}
74+
}
75+
}
76+
#endif

Sources/SwiftUIBackports/Shared/Label/Label.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,18 @@ extension Backport where Wrapped == Any {
9393

9494
/// Creates a label with a custom title and icon.
9595
public init(@ViewBuilder title: () -> Title, @ViewBuilder icon: () -> Icon) {
96-
config = .init(title: .init(content: title()), icon: .init(content: icon()))
96+
config = .init(title: .init(title()), icon: .init(icon()))
9797
}
9898

9999
@MainActor public var body: some View {
100-
if let style = style {
101-
style.makeBody(configuration: config.environment(environment))
100+
if #available(iOS 14, *) {
101+
SwiftUI.Label {
102+
config.title
103+
} icon: {
104+
config.icon
105+
}
102106
} else {
103-
DefaultLabelStyle()
104-
.makeBody(configuration: config.environment(environment))
107+
style.makeBody(configuration: config.environment(environment))
105108
}
106109
}
107110
}
@@ -156,7 +159,7 @@ extension Backport.Label where Wrapped == Any, Title == Text, Icon == Image {
156159

157160
}
158161

159-
extension Backport.Label where Wrapped == Any, Title == Backport.LabelStyleConfiguration.Title, Icon == Backport.LabelStyleConfiguration.Icon {
162+
extension Backport.Label where Wrapped == Any {
160163

161164
/// Creates a label representing the configuration of a style.
162165
///

Sources/SwiftUIBackports/Shared/Label/LabelConfiguration.swift

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,11 @@ extension Backport where Wrapped == Any {
99
/// The properties of a label.
1010
public struct LabelStyleConfiguration {
1111

12-
/// A type-erased title view of a label.
13-
public struct Title: View {
14-
let content: AnyView
15-
public var body: some View { content }
16-
init<Content: View>(content: Content) {
17-
self.content = .init(content)
18-
}
19-
}
20-
21-
/// A type-erased icon view of a label.
22-
public struct Icon: View {
23-
let content: AnyView
24-
public var body: some View { content }
25-
init<Content: View>(content: Content) {
26-
self.content = .init(content)
27-
}
28-
}
29-
3012
/// A description of the labeled item.
31-
public internal(set) var title: LabelStyleConfiguration.Title
13+
public internal(set) var title: AnyView
3214

3315
/// A symbolic representation of the labeled item.
34-
public internal(set) var icon: LabelStyleConfiguration.Icon
16+
public internal(set) var icon: AnyView
3517

3618
internal var environment: EnvironmentValues = .init()
3719

Sources/SwiftUIBackports/Shared/Label/LabelStyle.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ internal struct AnyLabelStyle: BackportLabelStyle {
5252
}
5353

5454
private struct BackportLabelStyleEnvironmentKey: EnvironmentKey {
55-
static var defaultValue: AnyLabelStyle? = nil
55+
static var defaultValue: AnyLabelStyle = .init(.automatic)
5656
}
5757

5858
internal extension EnvironmentValues {
59-
var backportLabelStyle: AnyLabelStyle? {
59+
var backportLabelStyle: AnyLabelStyle {
6060
get { self[BackportLabelStyleEnvironmentKey.self] }
6161
set { self[BackportLabelStyleEnvironmentKey.self] = newValue }
6262
}

Sources/SwiftUIBackports/Shared/Label/Styles/DefaultLabelStyle.swift

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,23 @@ extension Backport where Wrapped == Any {
1010
///
1111
/// You can also use ``LabelStyle/automatic`` to construct this style.
1212
public struct DefaultLabelStyle: BackportLabelStyle {
13+
private struct Label: View {
14+
let configuration: Configuration
15+
@State private var isToolbarElement: Bool = false
16+
17+
var body: some View {
18+
if isToolbarElement {
19+
IconOnlyLabelStyle().makeBody(configuration: configuration)
20+
} else {
21+
TitleAndIconLabelStyle().makeBody(configuration: configuration)
22+
.inspect { inspector in
23+
inspector.ancestor(ofType: UINavigationBar.self)
24+
} customize: { _ in
25+
isToolbarElement = true
26+
}
27+
}
28+
}
29+
}
1330

1431
public init() { }
1532

@@ -19,11 +36,8 @@ extension Backport where Wrapped == Any {
1936
/// hierarchy where this style is the current label style.
2037
///
2138
/// - Parameter configuration: The properties of the label.
22-
public func makeBody(configuration: DefaultLabelStyle.Configuration) -> some View {
23-
HStack {
24-
configuration.icon
25-
configuration.title
26-
}
39+
public func makeBody(configuration: Configuration) -> some View {
40+
Label(configuration: configuration)
2741
}
2842

2943
}

0 commit comments

Comments
 (0)