From 61da8d6ca36a776e14e5508a72b8285eefc37ba0 Mon Sep 17 00:00:00 2001 From: Roland Schmitz Date: Thu, 17 Aug 2023 20:11:36 +0200 Subject: [PATCH 1/6] Fix endless recursion by avoiding to link _delegates more than once In the overrides to willMove(toParent parent: UIViewController?) in Detents.swift and also InteractiveDismiss.swift the controller injects itself to the chain of delegates. Apparently the function is called more than once for the same controller if the device orientation changes. When a sheet is used with both .backport.presentationDetents([.medium]) and .backport.interactiveDismissDisabled(true) then multiple willMove calls will result in a cycle in the linked delegate which will then result in an endless recursion. To avoid the recursion and the crash we just check if _delegate has not been set before. Resolves: #62 --- Sources/SwiftUIBackports/iOS/Presentation/Detents.swift | 2 +- .../SwiftUIBackports/iOS/Presentation/InteractiveDismiss.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftUIBackports/iOS/Presentation/Detents.swift b/Sources/SwiftUIBackports/iOS/Presentation/Detents.swift index 4f259110..b4decad9 100644 --- a/Sources/SwiftUIBackports/iOS/Presentation/Detents.swift +++ b/Sources/SwiftUIBackports/iOS/Presentation/Detents.swift @@ -169,7 +169,7 @@ private extension Backport.Representable { override func willMove(toParent parent: UIViewController?) { super.willMove(toParent: parent) if let controller = parent?.sheetPresentationController { - if controller.delegate !== self { + if controller.delegate !== self && _delegate != nil { _delegate = controller.delegate controller.delegate = self } diff --git a/Sources/SwiftUIBackports/iOS/Presentation/InteractiveDismiss.swift b/Sources/SwiftUIBackports/iOS/Presentation/InteractiveDismiss.swift index 714b73b6..89d2f37c 100644 --- a/Sources/SwiftUIBackports/iOS/Presentation/InteractiveDismiss.swift +++ b/Sources/SwiftUIBackports/iOS/Presentation/InteractiveDismiss.swift @@ -194,7 +194,7 @@ private extension Backport.Representable { override func willMove(toParent parent: UIViewController?) { super.willMove(toParent: parent) if let controller = parent?.presentationController { - if controller.delegate !== self { + if controller.delegate !== self && _delegate != nil { _delegate = controller.delegate controller.delegate = self } From d0b15a656380d0cbb1f15affd2a10af9bfa67328 Mon Sep 17 00:00:00 2001 From: Roland Schmitz Date: Thu, 31 Aug 2023 15:54:45 +0200 Subject: [PATCH 2/6] Only assign delegate, if it is not already set. In the overrides to willMove(toParent parent: UIViewController?) in Detents.swift and also InteractiveDismiss.swift the controller injects itself to the chain of delegates. Apparently the function is called more than once for the same controller if the device orientation changes. When a sheet is used with both .backport.presentationDetents([.medium]) and .backport.interactiveDismissDisabled(true) then multiple willMove calls will result in a cycle in the linked delegate which will then result in an endless recursion. To avoid the recursion and the crash we just check if _delegate has not been set before. Resolves: #62 --- Sources/SwiftUIBackports/iOS/Presentation/Detents.swift | 2 +- .../SwiftUIBackports/iOS/Presentation/InteractiveDismiss.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftUIBackports/iOS/Presentation/Detents.swift b/Sources/SwiftUIBackports/iOS/Presentation/Detents.swift index b4decad9..c27cdb14 100644 --- a/Sources/SwiftUIBackports/iOS/Presentation/Detents.swift +++ b/Sources/SwiftUIBackports/iOS/Presentation/Detents.swift @@ -169,7 +169,7 @@ private extension Backport.Representable { override func willMove(toParent parent: UIViewController?) { super.willMove(toParent: parent) if let controller = parent?.sheetPresentationController { - if controller.delegate !== self && _delegate != nil { + if controller.delegate !== self && _delegate == nil { _delegate = controller.delegate controller.delegate = self } diff --git a/Sources/SwiftUIBackports/iOS/Presentation/InteractiveDismiss.swift b/Sources/SwiftUIBackports/iOS/Presentation/InteractiveDismiss.swift index 89d2f37c..a1eeb0b7 100644 --- a/Sources/SwiftUIBackports/iOS/Presentation/InteractiveDismiss.swift +++ b/Sources/SwiftUIBackports/iOS/Presentation/InteractiveDismiss.swift @@ -194,7 +194,7 @@ private extension Backport.Representable { override func willMove(toParent parent: UIViewController?) { super.willMove(toParent: parent) if let controller = parent?.presentationController { - if controller.delegate !== self && _delegate != nil { + if controller.delegate !== self && _delegate == nil { _delegate = controller.delegate controller.delegate = self } From 26cc15cdd29047269ff0f8494aa1df45f9e83a4b Mon Sep 17 00:00:00 2001 From: Hao Guan <10684225+hguandl@users.noreply.github.com> Date: Sun, 20 Aug 2023 04:16:54 +1000 Subject: [PATCH 3/6] Fix iOS 14 onChange in the task view modifier --- Sources/SwiftUIBackports/Shared/Task/Task.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/SwiftUIBackports/Shared/Task/Task.swift b/Sources/SwiftUIBackports/Shared/Task/Task.swift index 26123129..37a0d5f8 100644 --- a/Sources/SwiftUIBackports/Shared/Task/Task.swift +++ b/Sources/SwiftUIBackports/Shared/Task/Task.swift @@ -140,22 +140,22 @@ private struct TaskModifier: ViewModifier { var action: @Sendable () async -> Void @State private var task: Task? - @State private var oldID: ID + @State private var publisher = PassthroughSubject<(), Never>() init(id: ID, priority: TaskPriority, action: @Sendable @escaping () async -> Void) { self.id = id self.priority = priority self.action = action - _oldID = .init(initialValue: id) } func body(content: Content) -> some View { content - .onReceive(Just(id)) { newID in - guard newID != oldID else { return } + .backport.onChange(of: id) { _ in + publisher.send() + } + .onReceive(publisher) { _ in task?.cancel() task = Task(priority: priority, operation: action) - oldID = newID } .onAppear { task?.cancel() From 8bdf0398d91852697ecb76c4b955db030cf12d60 Mon Sep 17 00:00:00 2001 From: Roland Schmitz Date: Sat, 9 Sep 2023 16:44:09 +0200 Subject: [PATCH 4/6] Only assign delegate in the Coordinator for FocusModifier, if it is not already set to guard against multiple calls and avoid an endless loop in responds(to:) Resolves: #62 and also #61 --- Package.resolved | 4 ++-- Sources/SwiftUIBackports/iOS/FocusState/ViewFocused.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Package.resolved b/Package.resolved index 4ef1837b..6679401f 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/shaps80/SwiftBackports", "state" : { - "revision" : "fafbeabf78b7e364abbbb7565cdfeee42af16211", - "version" : "1.0.2" + "revision" : "ddca6a237c1ba2291d5a3cc47ec8480ce6e9f805", + "version" : "1.0.3" } } ], diff --git a/Sources/SwiftUIBackports/iOS/FocusState/ViewFocused.swift b/Sources/SwiftUIBackports/iOS/FocusState/ViewFocused.swift index c7366086..06864346 100644 --- a/Sources/SwiftUIBackports/iOS/FocusState/ViewFocused.swift +++ b/Sources/SwiftUIBackports/iOS/FocusState/ViewFocused.swift @@ -60,7 +60,7 @@ private final class Coordinator: NSObject, ObservableObject, UITextFieldDelegate func observe(field: UITextField) { self.field = field - if field.delegate !== self { + if field.delegate !== self && _delegate == nil { _delegate = field.delegate field.delegate = self } From 9565cc230d8243fc137ba3ed4e8d0155a4769dfc Mon Sep 17 00:00:00 2001 From: Ihor Shevchuk Date: Sat, 28 Oct 2023 17:39:06 +0300 Subject: [PATCH 5/6] Added Named Colors and CGColor providers to fix issue when named colors can't be used to set foreground color of TextEditor --- .../iOS/TextEditor/ColorProviders.swift | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/Sources/SwiftUIBackports/iOS/TextEditor/ColorProviders.swift b/Sources/SwiftUIBackports/iOS/TextEditor/ColorProviders.swift index 3913f21b..215392e5 100644 --- a/Sources/SwiftUIBackports/iOS/TextEditor/ColorProviders.swift +++ b/Sources/SwiftUIBackports/iOS/TextEditor/ColorProviders.swift @@ -56,6 +56,39 @@ struct UICachedDeviceRGBColor: ColorProvider { } } +struct NamedColor: ColorProvider { + var color: UIColor? + + init(provider: Any) { + let mirror = Mirror(reflecting: provider) + if let colorName = mirror.descendant("name") as? String { + let bundle = mirror.descendant("bundle") as? Bundle + color = UIColor(named: colorName, in: bundle, compatibleWith: nil) + } + } +} + +struct UICGColor: ColorProvider { + var color: UIColor? + + init(provider: Any) { + if let color = provider as? UIColor { + self.color = color + } + } +} + +struct NSCFType: ColorProvider { + var color: UIColor? + + init(provider: Any) { + let isCGColor = CFGetTypeID(provider as CFTypeRef) == CGColor.typeID + if isCGColor { + color = UIColor(cgColor: provider as! CGColor) + } + } +} + struct UIDynamicCatalogSystemColor: ColorProvider { var color: UIColor? } @@ -196,6 +229,12 @@ func resolveColorProvider(_ provider: Any) -> ColorProvider? { return DisplayP3(provider: provider) case "Resolved": return UICachedDeviceRGBColor(provider: provider) + case String(describing: NamedColor.self): + return NamedColor(provider: provider) + case String(describing: UICGColor.self): + return UICGColor(provider: provider) + case "__\(String(describing: NSCFType.self))": + return NSCFType(provider: provider) default: print("Unhandled color provider: \(String(describing: type(of: provider)))") return nil From b26d875ddbb84c7c1e305d30e3226b74a6d0bd8b Mon Sep 17 00:00:00 2001 From: Stephen Woodford Date: Thu, 4 Jan 2024 16:10:09 -0600 Subject: [PATCH 6/6] Remove deprecation warnings for using contentConfiguration before iOS 16 since its used for UIHostingConfiguration --- .../UIHostingConfiguration/Cells/UICollectionViewCell.swift | 4 ++-- .../iOS/UIHostingConfiguration/Cells/UITableViewCell.swift | 4 ++-- .../iOS/UIHostingConfiguration/UIContentConfiguration.swift | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftUIBackports/iOS/UIHostingConfiguration/Cells/UICollectionViewCell.swift b/Sources/SwiftUIBackports/iOS/UIHostingConfiguration/Cells/UICollectionViewCell.swift index 0fa86ba3..0ac11e97 100644 --- a/Sources/SwiftUIBackports/iOS/UIHostingConfiguration/Cells/UICollectionViewCell.swift +++ b/Sources/SwiftUIBackports/iOS/UIHostingConfiguration/Cells/UICollectionViewCell.swift @@ -14,8 +14,8 @@ extension UICollectionViewCell { } } -@available(iOS, deprecated: 14) -@available(tvOS, deprecated: 14) +@available(iOS, deprecated: 16) +@available(tvOS, deprecated: 16) @available(macOS, unavailable) @available(watchOS, unavailable) extension Backport where Wrapped: UICollectionViewCell { diff --git a/Sources/SwiftUIBackports/iOS/UIHostingConfiguration/Cells/UITableViewCell.swift b/Sources/SwiftUIBackports/iOS/UIHostingConfiguration/Cells/UITableViewCell.swift index abc3c4db..e60dcd5f 100644 --- a/Sources/SwiftUIBackports/iOS/UIHostingConfiguration/Cells/UITableViewCell.swift +++ b/Sources/SwiftUIBackports/iOS/UIHostingConfiguration/Cells/UITableViewCell.swift @@ -14,8 +14,8 @@ extension UITableViewCell { } } -@available(iOS, deprecated: 14) -@available(tvOS, deprecated: 14) +@available(iOS, deprecated: 16) +@available(tvOS, deprecated: 16) @available(macOS, unavailable) @available(watchOS, unavailable) extension Backport where Wrapped: UITableViewCell { diff --git a/Sources/SwiftUIBackports/iOS/UIHostingConfiguration/UIContentConfiguration.swift b/Sources/SwiftUIBackports/iOS/UIHostingConfiguration/UIContentConfiguration.swift index 4b87c2c2..ba953ad2 100644 --- a/Sources/SwiftUIBackports/iOS/UIHostingConfiguration/UIContentConfiguration.swift +++ b/Sources/SwiftUIBackports/iOS/UIHostingConfiguration/UIContentConfiguration.swift @@ -7,8 +7,8 @@ import SwiftUI /// default styling and content for a content view. The content configuration encapsulates /// all of the supported properties and behaviors for content view customization. /// You use the configuration to create the content view. -@available(iOS, deprecated: 14) -@available(tvOS, deprecated: 14) +@available(iOS, deprecated: 16) +@available(tvOS, deprecated: 16) @available(macOS, unavailable) @available(watchOS, unavailable) public protocol BackportUIContentConfiguration {