From 434f58630607708a6e2fdcb1840999889a1d1052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20B=C4=85k?= Date: Thu, 4 Jul 2024 03:02:58 +0200 Subject: [PATCH 01/73] Add `.editorconfig` for consistent code formatting (#179) --- .editorconfig | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..410ff6c9e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +# editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true From 1a781f0a37ee42ab372bb7d032c2e33636636c7e Mon Sep 17 00:00:00 2001 From: Brandon Williams <135203+mbrandonw@users.noreply.github.com> Date: Fri, 19 Jul 2024 07:10:47 -0500 Subject: [PATCH 02/73] Update Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 83e13b102..d4081517e 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ test: -workspace SwiftUINavigation.xcworkspace \ -scheme SwiftUINavigation \ -destination platform="$(PLATFORM_TVOS)" - xcodebuild \ + xcodebuild test \ -workspace SwiftUINavigation.xcworkspace \ -scheme SwiftUINavigation \ -destination platform="$(PLATFORM_WATCHOS)" From bc0e37936a6a09a47b34cf655d0059f1fd4dd38d Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 22 Jul 2024 16:28:30 -0700 Subject: [PATCH 03/73] Swift Testing support for runtime warnings (#181) * Swift Testing support for runtime warnings * wip * bump * wip * wip * wip * wip * wip --------- Co-authored-by: Brandon Williams --- Examples/Examples.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 222 ++++++++++-------- Package.resolved | 24 +- Package.swift | 9 +- Package@swift-6.0.swift | 9 +- .../Internal/Deprecations.swift | 113 +++++++-- .../SwiftUINavigationCore/ButtonState.swift | 3 +- .../Internal/RuntimeWarnings.swift | 84 ------- .../xcshareddata/swiftpm/Package.resolved | 39 +-- .../SwiftUINavigationTests/BindingTests.swift | 4 + .../ButtonStateTests.swift | 3 +- .../SwiftUINavigationTests.swift | 2 + 12 files changed, 264 insertions(+), 250 deletions(-) delete mode 100644 Sources/SwiftUINavigationCore/Internal/RuntimeWarnings.swift diff --git a/Examples/Examples.xcodeproj/project.pbxproj b/Examples/Examples.xcodeproj/project.pbxproj index 3eac9bf07..22c0a4c45 100644 --- a/Examples/Examples.xcodeproj/project.pbxproj +++ b/Examples/Examples.xcodeproj/project.pbxproj @@ -561,7 +561,7 @@ repositoryURL = "http://github.com/pointfreeco/swift-dependencies"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 1.0.0; + minimumVersion = 1.3.4; }; }; DCE73E032947D063004EE92E /* XCRemoteSwiftPackageReference "swift-tagged" */ = { diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f33c147a9..dec6c15a3 100644 --- a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,106 +1,122 @@ { - "object": { - "pins": [ - { - "package": "combine-schedulers", - "repositoryURL": "https://github.com/pointfreeco/combine-schedulers", - "state": { - "branch": null, - "revision": "9dc9cbe4bc45c65164fa653a563d8d8db61b09bb", - "version": "1.0.0" - } - }, - { - "package": "swift-case-paths", - "repositoryURL": "https://github.com/pointfreeco/swift-case-paths", - "state": { - "branch": null, - "revision": "5da6989aae464f324eef5c5b52bdb7974725ab81", - "version": "1.0.0" - } - }, - { - "package": "swift-clocks", - "repositoryURL": "https://github.com/pointfreeco/swift-clocks", - "state": { - "branch": null, - "revision": "d1fd837326aa719bee979bdde1f53cd5797443eb", - "version": "1.0.0" - } - }, - { - "package": "swift-collections", - "repositoryURL": "https://github.com/apple/swift-collections", - "state": { - "branch": null, - "revision": "937e904258d22af6e447a0b72c0bc67583ef64a2", - "version": "1.0.4" - } - }, - { - "package": "swift-concurrency-extras", - "repositoryURL": "https://github.com/pointfreeco/swift-concurrency-extras", - "state": { - "branch": null, - "revision": "ea631ce892687f5432a833312292b80db238186a", - "version": "1.0.0" - } - }, - { - "package": "swift-custom-dump", - "repositoryURL": "https://github.com/pointfreeco/swift-custom-dump", - "state": { - "branch": null, - "revision": "edd66cace818e1b1c6f1b3349bb1d8e00d6f8b01", - "version": "1.0.0" - } - }, - { - "package": "swift-dependencies", - "repositoryURL": "http://github.com/pointfreeco/swift-dependencies", - "state": { - "branch": null, - "revision": "4e1eb6e28afe723286d8cc60611237ffbddba7c5", - "version": "1.0.0" - } - }, - { - "package": "SwiftDocCPlugin", - "repositoryURL": "https://github.com/apple/swift-docc-plugin", - "state": { - "branch": null, - "revision": "3303b164430d9a7055ba484c8ead67a52f7b74f6", - "version": "1.0.0" - } - }, - { - "package": "swift-identified-collections", - "repositoryURL": "https://github.com/pointfreeco/swift-identified-collections.git", - "state": { - "branch": null, - "revision": "d1e45f3e1eee2c9193f5369fa9d70a6ddad635e8", - "version": "1.0.0" - } - }, - { - "package": "swift-tagged", - "repositoryURL": "https://github.com/pointfreeco/swift-tagged.git", - "state": { - "branch": null, - "revision": "af06825aaa6adffd636c10a2570b2010c7c07e6a", - "version": "0.9.0" - } - }, - { - "package": "xctest-dynamic-overlay", - "repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay", - "state": { - "branch": null, - "revision": "23cbf2294e350076ea4dbd7d5d047c1e76b03631", - "version": "1.0.2" - } + "pins" : [ + { + "identity" : "combine-schedulers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/combine-schedulers", + "state" : { + "revision" : "487a4d151e795a5e076a7e7aedcd13c2ebff6c31", + "version" : "1.0.1" } - ] - }, - "version": 1 + }, + { + "identity" : "swift-case-paths", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-case-paths", + "state" : { + "revision" : "031704ba0634b45e02fe875b8ddddc7f30a07f49", + "version" : "1.5.3" + } + }, + { + "identity" : "swift-clocks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-clocks", + "state" : { + "revision" : "eb64eacfed55635a771e3410f9c91de46cf5c6a0", + "version" : "1.0.3" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "revision" : "3d2dc41a01f9e49d84f0a3925fb858bed64f702d", + "version" : "1.1.2" + } + }, + { + "identity" : "swift-concurrency-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-concurrency-extras", + "state" : { + "revision" : "bb5059bde9022d69ac516803f4f227d8ac967f71", + "version" : "1.1.0" + } + }, + { + "identity" : "swift-custom-dump", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-custom-dump", + "state" : { + "revision" : "d237304f42af07f22563aa4cc2d7e2cfb25da82e", + "version" : "1.3.1" + } + }, + { + "identity" : "swift-dependencies", + "kind" : "remoteSourceControl", + "location" : "http://github.com/pointfreeco/swift-dependencies", + "state" : { + "revision" : "52018827ce21e482a36e3795bea2666b3898164c", + "version" : "1.3.4" + } + }, + { + "identity" : "swift-docc-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-docc-plugin", + "state" : { + "revision" : "26ac5758409154cc448d7ab82389c520fa8a8247", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-docc-symbolkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-docc-symbolkit", + "state" : { + "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", + "version" : "1.0.0" + } + }, + { + "identity" : "swift-identified-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-identified-collections.git", + "state" : { + "revision" : "2f5ab6e091dd032b63dacbda052405756010dc3b", + "version" : "1.1.0" + } + }, + { + "identity" : "swift-issue-reporting", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-issue-reporting", + "state" : { + "branch" : "1.2.0", + "revision" : "926f43898706eaa127db79ac42138e1ad7e85a3f" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-syntax", + "state" : { + "revision" : "4c6cc0a3b9e8f14b3ae2307c5ccae4de6167ac2c", + "version" : "600.0.0-prerelease-2024-06-12" + } + }, + { + "identity" : "swift-tagged", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-tagged.git", + "state" : { + "revision" : "3907a9438f5b57d317001dc99f3f11b46882272b", + "version" : "0.10.0" + } + } + ], + "version" : 2 } diff --git a/Package.resolved b/Package.resolved index dc5a3d88d..9a54b0f91 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "b871e5ed11a23e52c2896a92ce2c829982ff8619", - "version" : "1.4.2" + "revision" : "031704ba0634b45e02fe875b8ddddc7f30a07f49", + "version" : "1.5.3" } }, { @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "f01efb26f3a192a0e88dcdb7c3c391ec2fc25d9c", - "version" : "1.3.0" + "revision" : "d237304f42af07f22563aa4cc2d7e2cfb25da82e", + "version" : "1.3.1" } }, { @@ -37,21 +37,21 @@ } }, { - "identity" : "swift-syntax", + "identity" : "swift-issue-reporting", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-syntax", + "location" : "https://github.com/pointfreeco/swift-issue-reporting", "state" : { - "revision" : "303e5c5c36d6a558407d364878df131c3546fad8", - "version" : "510.0.2" + "branch" : "1.2.0", + "revision" : "926f43898706eaa127db79ac42138e1ad7e85a3f" } }, { - "identity" : "xctest-dynamic-overlay", + "identity" : "swift-syntax", "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "location" : "https://github.com/swiftlang/swift-syntax", "state" : { - "revision" : "6f30bdba373bbd7fbfe241dddd732651f2fbd1e2", - "version" : "1.1.2" + "revision" : "4c6cc0a3b9e8f14b3ae2307c5ccae4de6167ac2c", + "version" : "600.0.0-prerelease-2024-06-12" } } ], diff --git a/Package.swift b/Package.swift index 1af837983..63f72c632 100644 --- a/Package.swift +++ b/Package.swift @@ -22,9 +22,9 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), - .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.2.2"), - .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.0.0"), - .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.0.0"), + .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.5.3"), + .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.1"), + .package(url: "https://github.com/pointfreeco/swift-issue-reporting", branch: "1.2.0"), ], targets: [ .target( @@ -32,6 +32,7 @@ let package = Package( dependencies: [ "SwiftUINavigationCore", .product(name: "CasePaths", package: "swift-case-paths"), + .product(name: "IssueReporting", package: "swift-issue-reporting"), ] ), .testTarget( @@ -44,7 +45,7 @@ let package = Package( name: "SwiftUINavigationCore", dependencies: [ .product(name: "CustomDump", package: "swift-custom-dump"), - .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"), + .product(name: "IssueReporting", package: "swift-issue-reporting"), ] ), ] diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index b3a8b94dc..b565f5ed6 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -22,9 +22,9 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), - .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.2.2"), - .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.0.0"), - .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.0.0"), + .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.5.3"), + .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.1"), + .package(url: "https://github.com/pointfreeco/swift-issue-reporting", from: "1.2.0"), ], targets: [ .target( @@ -32,6 +32,7 @@ let package = Package( dependencies: [ "SwiftUINavigationCore", .product(name: "CasePaths", package: "swift-case-paths"), + .product(name: "IssueReporting", package: "swift-issue-reporting"), ] ), .testTarget( @@ -44,7 +45,7 @@ let package = Package( name: "SwiftUINavigationCore", dependencies: [ .product(name: "CustomDump", package: "swift-custom-dump"), - .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"), + .product(name: "IssueReporting", package: "swift-issue-reporting"), ] ), ], diff --git a/Sources/SwiftUINavigation/Internal/Deprecations.swift b/Sources/SwiftUINavigation/Internal/Deprecations.swift index 641171d0a..c9ddd73d3 100644 --- a/Sources/SwiftUINavigation/Internal/Deprecations.swift +++ b/Sources/SwiftUINavigation/Internal/Deprecations.swift @@ -1,6 +1,6 @@ #if canImport(SwiftUI) + import IssueReporting import SwiftUI - @_spi(RuntimeWarn) import SwiftUINavigationCore // NB: Deprecated after 1.3.0 @@ -862,8 +862,10 @@ public init( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> CaseLet ) where @@ -874,7 +876,11 @@ { self.init(`enum`) { content() - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } @@ -911,8 +917,10 @@ public init( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> TupleView< ( CaseLet, @@ -933,7 +941,11 @@ self.init(`enum`) { content.value.0 content.value.1 - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } @@ -981,8 +993,10 @@ public init( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> TupleView< ( CaseLet, @@ -1008,7 +1022,11 @@ content.value.0 content.value.1 content.value.2 - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } @@ -1068,8 +1086,10 @@ Case4: Sendable, Content4 >( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> TupleView< ( CaseLet, @@ -1100,7 +1120,11 @@ content.value.1 content.value.2 content.value.3 - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } @@ -1168,8 +1192,10 @@ Case5: Sendable, Content5 >( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> TupleView< ( CaseLet, @@ -1205,7 +1231,11 @@ content.value.2 content.value.3 content.value.4 - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } @@ -1281,8 +1311,10 @@ Case6: Sendable, Content6 >( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> TupleView< ( CaseLet, @@ -1323,7 +1355,11 @@ content.value.3 content.value.4 content.value.5 - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } @@ -1407,8 +1443,10 @@ Case7: Sendable, Content7 >( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> TupleView< ( CaseLet, @@ -1454,7 +1492,11 @@ content.value.4 content.value.5 content.value.6 - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } @@ -1546,8 +1588,10 @@ Case8: Sendable, Content8 >( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> TupleView< ( CaseLet, @@ -1598,7 +1642,11 @@ content.value.5 content.value.6 content.value.7 - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } @@ -1698,8 +1746,10 @@ Case9: Sendable, Content9 >( _ enum: Binding, - file: StaticString = #fileID, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, line: UInt = #line, + column: UInt = #column, @ViewBuilder content: () -> TupleView< ( CaseLet, @@ -1755,25 +1805,32 @@ content.value.6 content.value.7 content.value.8 - Default { _ExhaustivityCheckView(file: file, line: line) } + Default { + _ExhaustivityCheckView( + fileID: fileID, filePath: filePath, line: line, column: column + ) + } } } } public struct _ExhaustivityCheckView: View { @EnvironmentObject private var `enum`: BindingObject - let file: StaticString + let fileID: StaticString + let filePath: StaticString let line: UInt + let column: UInt public var body: some View { #if DEBUG let message = """ - Warning: Switch.body@\(self.file):\(self.line) + Warning: Switch.body@\(fileID):\(line) "Switch" did not handle "\(describeCase(self.enum.wrappedValue.wrappedValue))" - Make sure that you exhaustively provide a "CaseLet" view for each case in "\(Enum.self)", \ - provide a "Default" view at the end of the "Switch", or use an "IfCaseLet" view instead. + Make sure that you exhaustively provide a "CaseLet" view for each case in \ + "\(Enum.self)", provide a "Default" view at the end of the "Switch", or use an \ + "IfCaseLet" view instead. """ VStack(spacing: 17) { self.exclamation() @@ -1785,7 +1842,15 @@ .foregroundColor(.white) .padding() .background(Color.red.edgesIgnoringSafeArea(.all)) - .onAppear { runtimeWarn(message, file: self.file, line: self.line) } + .onAppear { + reportIssue( + message, + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + } #else EmptyView() #endif diff --git a/Sources/SwiftUINavigationCore/ButtonState.swift b/Sources/SwiftUINavigationCore/ButtonState.swift index efe8acbcf..35494883a 100644 --- a/Sources/SwiftUINavigationCore/ButtonState.swift +++ b/Sources/SwiftUINavigationCore/ButtonState.swift @@ -1,5 +1,6 @@ #if canImport(SwiftUI) import CustomDump + import IssueReporting import SwiftUI public struct ButtonState: Identifiable { @@ -79,7 +80,7 @@ case let .animatedSend(action, _): var output = "" customDump(self.action, to: &output, indent: 4) - runtimeWarn( + reportIssue( """ An animated action was performed asynchronously: … diff --git a/Sources/SwiftUINavigationCore/Internal/RuntimeWarnings.swift b/Sources/SwiftUINavigationCore/Internal/RuntimeWarnings.swift deleted file mode 100644 index 6212de6f2..000000000 --- a/Sources/SwiftUINavigationCore/Internal/RuntimeWarnings.swift +++ /dev/null @@ -1,84 +0,0 @@ -#if canImport(SwiftUI) - @_spi(RuntimeWarn) - @_transparent - @inline(__always) - public func runtimeWarn( - _ message: @autoclosure () -> String, - category: String? = "SwiftUINavigation", - file: StaticString? = nil, - line: UInt? = nil - ) { - #if DEBUG - let message = message() - let category = category ?? "Runtime Warning" - if _XCTIsTesting { - if let file, let line { - XCTFail(message, file: file, line: line) - } else { - XCTFail(message) - } - } else { - #if canImport(os) - os_log( - .fault, - dso: dso.wrappedValue, - log: OSLog(subsystem: "com.apple.runtime-issues", category: category), - "%@", - message - ) - #else - fputs("\(formatter.string(from: Date())) [\(category)] \(message)\n", stderr) - #endif - } - #endif - } - - #if DEBUG - import XCTestDynamicOverlay - - #if canImport(os) - import os - import Foundation - - // NB: Xcode runtime warnings offer a much better experience than traditional assertions and - // breakpoints, but Apple provides no means of creating custom runtime warnings ourselves. - // To work around this, we hook into SwiftUI's runtime issue delivery mechanism, instead. - // - // Feedback filed: https://gist.github.com/stephencelis/a8d06383ed6ccde3e5ef5d1b3ad52bbc - @usableFromInline - let dso = UncheckedSendable( - { - let count = _dyld_image_count() - for i in 0..: @unchecked Sendable { - @usableFromInline - var wrappedValue: Value - init(_ value: Value) { - self.wrappedValue = value - } - } - #else - import Foundation - - @usableFromInline - let formatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd HH:MM:SS.sssZ" - return formatter - }() - #endif - #endif -#endif // canImport(SwiftUI) diff --git a/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7416d722b..d4778940b 100644 --- a/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "b871e5ed11a23e52c2896a92ce2c829982ff8619", - "version" : "1.4.2" + "revision" : "12bc5b9191b62ee62cafecbfed953fbb1e1554cd", + "version" : "1.5.2" } }, { @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections", "state" : { - "revision" : "d029d9d39c87bed85b1c50adee7c41795261a192", - "version" : "1.0.6" + "revision" : "3d2dc41a01f9e49d84f0a3925fb858bed64f702d", + "version" : "1.1.2" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "aedcf6f4cd486ccef5b312ccac85d4b3f6e58605", - "version" : "1.1.2" + "revision" : "f01efb26f3a192a0e88dcdb7c3c391ec2fc25d9c", + "version" : "1.3.0" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "http://github.com/pointfreeco/swift-dependencies", "state" : { - "revision" : "c31b1445c4fae49e6fdb75496b895a3653f6aefc", - "version" : "1.1.5" + "revision" : "d80613633e76d1ef86f41926e72fbef6a2f77d9c", + "version" : "1.3.3" } }, { @@ -86,17 +86,26 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-identified-collections.git", "state" : { - "revision" : "d1e45f3e1eee2c9193f5369fa9d70a6ddad635e8", - "version" : "1.0.0" + "revision" : "2f5ab6e091dd032b63dacbda052405756010dc3b", + "version" : "1.1.0" + } + }, + { + "identity" : "swift-issue-reporting", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-issue-reporting", + "state" : { + "branch" : "1.2.0", + "revision" : "926f43898706eaa127db79ac42138e1ad7e85a3f" } }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-syntax.git", + "location" : "https://github.com/swiftlang/swift-syntax", "state" : { - "revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036", - "version" : "509.0.2" + "revision" : "4c6cc0a3b9e8f14b3ae2307c5ccae4de6167ac2c", + "version" : "600.0.0-prerelease-2024-06-12" } }, { @@ -113,8 +122,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", "state" : { - "revision" : "23cbf2294e350076ea4dbd7d5d047c1e76b03631", - "version" : "1.0.2" + "revision" : "926f43898706eaa127db79ac42138e1ad7e85a3f", + "version" : "1.2.0" } } ], diff --git a/Tests/SwiftUINavigationTests/BindingTests.swift b/Tests/SwiftUINavigationTests/BindingTests.swift index ba85a092e..8d45a3888 100644 --- a/Tests/SwiftUINavigationTests/BindingTests.swift +++ b/Tests/SwiftUINavigationTests/BindingTests.swift @@ -12,6 +12,7 @@ case outOfStock(isOnBackOrder: Bool) } + @MainActor func testCaseLookup() throws { @Binding var status: Status _status = Binding(initialValue: .inStock(quantity: 1)) @@ -22,6 +23,7 @@ XCTAssertEqual(status, .inStock(quantity: 2)) } + @MainActor func testCaseCannotReplaceOtherCase() throws { @Binding var status: Status _status = Binding(initialValue: .inStock(quantity: 1)) @@ -34,6 +36,7 @@ XCTAssertEqual(status, .outOfStock(isOnBackOrder: true)) } + @MainActor func testDestinationCannotReplaceOtherDestination() throws { #if os(iOS) || os(macOS) try XCTSkipIf(ProcessInfo.processInfo.environment["CI"] != nil) @@ -52,6 +55,7 @@ } extension Binding { + @MainActor fileprivate init(initialValue: Value) { var value = initialValue self.init( diff --git a/Tests/SwiftUINavigationTests/ButtonStateTests.swift b/Tests/SwiftUINavigationTests/ButtonStateTests.swift index 857d26806..9cf8c2f24 100644 --- a/Tests/SwiftUINavigationTests/ButtonStateTests.swift +++ b/Tests/SwiftUINavigationTests/ButtonStateTests.swift @@ -5,11 +5,10 @@ import XCTest final class ButtonStateTests: XCTestCase { - @MainActor func testAsyncAnimationWarning() async { XCTExpectFailure { $0.compactDescription == """ - An animated action was performed asynchronously: … + failed - An animated action was performed asynchronously: … Action: ButtonStateAction.send( diff --git a/Tests/SwiftUINavigationTests/SwiftUINavigationTests.swift b/Tests/SwiftUINavigationTests/SwiftUINavigationTests.swift index abd3bda8e..57718e2ca 100644 --- a/Tests/SwiftUINavigationTests/SwiftUINavigationTests.swift +++ b/Tests/SwiftUINavigationTests/SwiftUINavigationTests.swift @@ -5,6 +5,7 @@ @testable import SwiftUINavigation final class SwiftUINavigationTests: XCTestCase { + @MainActor func testBindingUnwrap() throws { var value: Int? let binding = Binding(get: { value }, set: { value = $0 }) @@ -29,6 +30,7 @@ XCTAssertEqual(unwrapped.wrappedValue, 1729) } + @MainActor func testBindingCase() throws { struct MyError: Error, Equatable {} var value: Result? = nil From f1d9d6a2e817cbed681c54b9975d78bf8d66ba38 Mon Sep 17 00:00:00 2001 From: stephencelis Date: Mon, 22 Jul 2024 23:30:03 +0000 Subject: [PATCH 04/73] Run swift-format --- .../xcshareddata/swiftpm/Package.resolved | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved index d4778940b..dec6c15a3 100644 --- a/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/combine-schedulers", "state" : { - "revision" : "9dc9cbe4bc45c65164fa653a563d8d8db61b09bb", - "version" : "1.0.0" + "revision" : "487a4d151e795a5e076a7e7aedcd13c2ebff6c31", + "version" : "1.0.1" } }, { @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "12bc5b9191b62ee62cafecbfed953fbb1e1554cd", - "version" : "1.5.2" + "revision" : "031704ba0634b45e02fe875b8ddddc7f30a07f49", + "version" : "1.5.3" } }, { @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-clocks", "state" : { - "revision" : "a8421d68068d8f45fbceb418fbf22c5dad4afd33", - "version" : "1.0.2" + "revision" : "eb64eacfed55635a771e3410f9c91de46cf5c6a0", + "version" : "1.0.3" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "f01efb26f3a192a0e88dcdb7c3c391ec2fc25d9c", - "version" : "1.3.0" + "revision" : "d237304f42af07f22563aa4cc2d7e2cfb25da82e", + "version" : "1.3.1" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "http://github.com/pointfreeco/swift-dependencies", "state" : { - "revision" : "d80613633e76d1ef86f41926e72fbef6a2f77d9c", - "version" : "1.3.3" + "revision" : "52018827ce21e482a36e3795bea2666b3898164c", + "version" : "1.3.4" } }, { @@ -116,15 +116,6 @@ "revision" : "3907a9438f5b57d317001dc99f3f11b46882272b", "version" : "0.10.0" } - }, - { - "identity" : "xctest-dynamic-overlay", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", - "state" : { - "revision" : "926f43898706eaa127db79ac42138e1ad7e85a3f", - "version" : "1.2.0" - } } ], "version" : 2 From 97f854044356ac082e7e698f39264cc035544d77 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 22 Jul 2024 16:37:42 -0700 Subject: [PATCH 05/73] wip --- Package.resolved | 4 ++-- Package.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Package.resolved b/Package.resolved index 9a54b0f91..7c0037409 100644 --- a/Package.resolved +++ b/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-issue-reporting", "state" : { - "branch" : "1.2.0", - "revision" : "926f43898706eaa127db79ac42138e1ad7e85a3f" + "revision" : "926f43898706eaa127db79ac42138e1ad7e85a3f", + "version" : "1.2.0" } }, { diff --git a/Package.swift b/Package.swift index 63f72c632..e68111d39 100644 --- a/Package.swift +++ b/Package.swift @@ -24,7 +24,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.5.3"), .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.1"), - .package(url: "https://github.com/pointfreeco/swift-issue-reporting", branch: "1.2.0"), + .package(url: "https://github.com/pointfreeco/swift-issue-reporting", from: "1.2.0"), ], targets: [ .target( From e4f6000387262e51d599f310468a4bef4e637651 Mon Sep 17 00:00:00 2001 From: stephencelis Date: Mon, 22 Jul 2024 23:39:39 +0000 Subject: [PATCH 06/73] Run swift-format --- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved index dec6c15a3..a7fbf9431 100644 --- a/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -95,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-issue-reporting", "state" : { - "branch" : "1.2.0", - "revision" : "926f43898706eaa127db79ac42138e1ad7e85a3f" + "revision" : "926f43898706eaa127db79ac42138e1ad7e85a3f", + "version" : "1.2.0" } }, { From 0adf7ccdd5c7906502d29076b6a6973439a18028 Mon Sep 17 00:00:00 2001 From: David Furman Date: Tue, 23 Jul 2024 12:14:21 -0700 Subject: [PATCH 07/73] Fix article links (#180) * Fix article links Fulfills https://github.com/pointfreeco/swiftui-navigation/issues/178 * Update references instead * revert brackets to parentheses --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bba0b2d03..871eed17c 100644 --- a/README.md +++ b/README.md @@ -124,9 +124,9 @@ This library is released under the MIT license. See [LICENSE](LICENSE) for detai [NavigationLink.init]: https://developer.apple.com/documentation/swiftui/navigationlink/init(destination:label:)-27n7s [TabView.init]: https://developer.apple.com/documentation/swiftui/tabview/init(content:) [case-paths-gh]: https://github.com/pointfreeco/swift-case-paths -[what-is-article]: https://pointfreeco.github.io/swiftui-navigation/main/documentation/swiftuinavigation/whatisnavigation -[nav-links-dests-article]: https://pointfreeco.github.io/swiftui-navigation/main/documentation/swiftuinavigation/navigation -[sheets-popovers-covers-article]: https://pointfreeco.github.io/swiftui-navigation/main/documentation/swiftuinavigation/sheetspopoverscovers -[alerts-dialogs-article]: https://pointfreeco.github.io/swiftui-navigation/main/documentation/swiftuinavigation/alertsdialogs -[bindings]: https://pointfreeco.github.io/swiftui-navigation/main/documentation/swiftuinavigation/bindings -[docs]: https://pointfreeco.github.io/swiftui-navigation/main/documentation/swiftuinavigation/ +[what-is-article]: https://swiftpackageindex.com/pointfreeco/swiftui-navigation/main/documentation/swiftuinavigation/whatisnavigation +[nav-links-dests-article]: https://swiftpackageindex.com/pointfreeco/swiftui-navigation/main/documentation/swiftuinavigation/navigation +[sheets-popovers-covers-article]: https://swiftpackageindex.com/pointfreeco/swiftui-navigation/main/documentation/swiftuinavigation/sheetspopoverscovers +[alerts-dialogs-article]: https://swiftpackageindex.com/pointfreeco/swiftui-navigation/main/documentation/swiftuinavigation/alertsdialogs +[bindings]: https://swiftpackageindex.com/pointfreeco/swiftui-navigation/main/documentation/swiftuinavigation/bindings +[docs]: https://swiftpackageindex.com/pointfreeco/swiftui-navigation/main/documentation/swiftuinavigation From b05be6ee6b26eec3c030148c444ae43358131b4d Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 24 Jul 2024 12:02:20 -0700 Subject: [PATCH 08/73] Point Issue Reporting to xctest-dynamic-overlay repo (#185) * Point Issue Reporting to xctest-dynamic-overlay repo Swift Package Manager honors redirects, but it appears to associate the path suffix with the package name, and this conflicts with package resolution in certain (but not all) cases. I think we have no choice but to roll back everything to point to the original xctest-dynamic-overlay URL and extract Issue Reporting to a dedicated repo. * wip --- Examples/Examples.xcodeproj/project.pbxproj | 2 +- Package.resolved | 24 +++++------ Package.swift | 10 ++--- .../xcshareddata/swiftpm/Package.resolved | 40 +++++++++---------- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/Examples/Examples.xcodeproj/project.pbxproj b/Examples/Examples.xcodeproj/project.pbxproj index 22c0a4c45..acc6de143 100644 --- a/Examples/Examples.xcodeproj/project.pbxproj +++ b/Examples/Examples.xcodeproj/project.pbxproj @@ -561,7 +561,7 @@ repositoryURL = "http://github.com/pointfreeco/swift-dependencies"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 1.3.4; + minimumVersion = 1.3.5; }; }; DCE73E032947D063004EE92E /* XCRemoteSwiftPackageReference "swift-tagged" */ = { diff --git a/Package.resolved b/Package.resolved index 7c0037409..9b70b8a69 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "031704ba0634b45e02fe875b8ddddc7f30a07f49", - "version" : "1.5.3" + "branch" : "xct-name", + "revision" : "593151ec13a564a79dc930cf57c82a67355b76c2" } }, { @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "d237304f42af07f22563aa4cc2d7e2cfb25da82e", - "version" : "1.3.1" + "branch" : "xct-name", + "revision" : "3af72bba805d9d91aecdc5c0df15d5f8b89de8de" } }, { @@ -37,21 +37,21 @@ } }, { - "identity" : "swift-issue-reporting", + "identity" : "swift-syntax", "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-issue-reporting", + "location" : "https://github.com/swiftlang/swift-syntax", "state" : { - "revision" : "926f43898706eaa127db79ac42138e1ad7e85a3f", - "version" : "1.2.0" + "revision" : "4c6cc0a3b9e8f14b3ae2307c5ccae4de6167ac2c", + "version" : "600.0.0-prerelease-2024-06-12" } }, { - "identity" : "swift-syntax", + "identity" : "xctest-dynamic-overlay", "kind" : "remoteSourceControl", - "location" : "https://github.com/swiftlang/swift-syntax", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", "state" : { - "revision" : "4c6cc0a3b9e8f14b3ae2307c5ccae4de6167ac2c", - "version" : "600.0.0-prerelease-2024-06-12" + "revision" : "357ca1e5dd31f613a1d43320870ebc219386a495", + "version" : "1.2.2" } } ], diff --git a/Package.swift b/Package.swift index e68111d39..63d94d4ae 100644 --- a/Package.swift +++ b/Package.swift @@ -22,9 +22,9 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), - .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.5.3"), - .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.1"), - .package(url: "https://github.com/pointfreeco/swift-issue-reporting", from: "1.2.0"), + .package(url: "https://github.com/pointfreeco/swift-case-paths", branch: "xct-name"), + .package(url: "https://github.com/pointfreeco/swift-custom-dump", branch: "xct-name"), + .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.2.2"), ], targets: [ .target( @@ -32,7 +32,7 @@ let package = Package( dependencies: [ "SwiftUINavigationCore", .product(name: "CasePaths", package: "swift-case-paths"), - .product(name: "IssueReporting", package: "swift-issue-reporting"), + .product(name: "IssueReporting", package: "xctest-dynamic-overlay"), ] ), .testTarget( @@ -45,7 +45,7 @@ let package = Package( name: "SwiftUINavigationCore", dependencies: [ .product(name: "CustomDump", package: "swift-custom-dump"), - .product(name: "IssueReporting", package: "swift-issue-reporting"), + .product(name: "IssueReporting", package: "xctest-dynamic-overlay"), ] ), ] diff --git a/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved index a7fbf9431..115ca181e 100644 --- a/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/combine-schedulers", "state" : { - "revision" : "487a4d151e795a5e076a7e7aedcd13c2ebff6c31", - "version" : "1.0.1" + "revision" : "9fa31f4403da54855f1e2aeaeff478f4f0e40b13", + "version" : "1.0.2" } }, { @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "031704ba0634b45e02fe875b8ddddc7f30a07f49", - "version" : "1.5.3" + "branch" : "xct-name", + "revision" : "593151ec13a564a79dc930cf57c82a67355b76c2" } }, { @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-clocks", "state" : { - "revision" : "eb64eacfed55635a771e3410f9c91de46cf5c6a0", - "version" : "1.0.3" + "revision" : "3581e280bf0d90c3fb9236fb23e75a5d8c46b533", + "version" : "1.0.4" } }, { @@ -39,7 +39,7 @@ { "identity" : "swift-concurrency-extras", "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-concurrency-extras", + "location" : "https://github.com/pointfreeco/swift-concurrency-extras.git", "state" : { "revision" : "bb5059bde9022d69ac516803f4f227d8ac967f71", "version" : "1.1.0" @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "d237304f42af07f22563aa4cc2d7e2cfb25da82e", - "version" : "1.3.1" + "branch" : "xct-name", + "revision" : "3af72bba805d9d91aecdc5c0df15d5f8b89de8de" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "http://github.com/pointfreeco/swift-dependencies", "state" : { - "revision" : "52018827ce21e482a36e3795bea2666b3898164c", - "version" : "1.3.4" + "revision" : "cc26d06125dbc913c6d9e8a905a5db0b994509e0", + "version" : "1.3.5" } }, { @@ -90,15 +90,6 @@ "version" : "1.1.0" } }, - { - "identity" : "swift-issue-reporting", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-issue-reporting", - "state" : { - "revision" : "926f43898706eaa127db79ac42138e1ad7e85a3f", - "version" : "1.2.0" - } - }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", @@ -116,6 +107,15 @@ "revision" : "3907a9438f5b57d317001dc99f3f11b46882272b", "version" : "0.10.0" } + }, + { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "357ca1e5dd31f613a1d43320870ebc219386a495", + "version" : "1.2.2" + } } ], "version" : 2 From 1b18b77a2e32250b45d3429f9928222981983826 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 24 Jul 2024 12:07:05 -0700 Subject: [PATCH 09/73] Bump versions --- Package.resolved | 8 ++++---- Package.swift | 4 ++-- Package@swift-6.0.swift | 8 ++++---- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Package.resolved b/Package.resolved index 9b70b8a69..031478ea7 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "branch" : "xct-name", - "revision" : "593151ec13a564a79dc930cf57c82a67355b76c2" + "revision" : "71344dd930fde41e8f3adafe260adcbb2fc2a3dc", + "version" : "1.5.4" } }, { @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "branch" : "xct-name", - "revision" : "3af72bba805d9d91aecdc5c0df15d5f8b89de8de" + "revision" : "aec6a73f5c1dc1f1be4f61888094b95cf995d973", + "version" : "1.3.2" } }, { diff --git a/Package.swift b/Package.swift index 63d94d4ae..6d602663b 100644 --- a/Package.swift +++ b/Package.swift @@ -22,8 +22,8 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), - .package(url: "https://github.com/pointfreeco/swift-case-paths", branch: "xct-name"), - .package(url: "https://github.com/pointfreeco/swift-custom-dump", branch: "xct-name"), + .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.5.4"), + .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.2"), .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.2.2"), ], targets: [ diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index b565f5ed6..c05de82e2 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -22,9 +22,9 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), - .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.5.3"), - .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.1"), - .package(url: "https://github.com/pointfreeco/swift-issue-reporting", from: "1.2.0"), + .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.5.4"), + .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.2"), + .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.2.2"), ], targets: [ .target( @@ -32,7 +32,7 @@ let package = Package( dependencies: [ "SwiftUINavigationCore", .product(name: "CasePaths", package: "swift-case-paths"), - .product(name: "IssueReporting", package: "swift-issue-reporting"), + .product(name: "IssueReporting", package: "xctest-dynamic-overlay"), ] ), .testTarget( diff --git a/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved index 115ca181e..ba1a2dd16 100644 --- a/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "branch" : "xct-name", - "revision" : "593151ec13a564a79dc930cf57c82a67355b76c2" + "revision" : "71344dd930fde41e8f3adafe260adcbb2fc2a3dc", + "version" : "1.5.4" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "branch" : "xct-name", - "revision" : "3af72bba805d9d91aecdc5c0df15d5f8b89de8de" + "revision" : "aec6a73f5c1dc1f1be4f61888094b95cf995d973", + "version" : "1.3.2" } }, { From fc91d591ebba1f90d65028ccb65c861e5979e898 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 24 Jul 2024 15:02:22 -0700 Subject: [PATCH 10/73] Update Package@swift-6.0.swift (#186) --- Package@swift-6.0.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index c05de82e2..80252b013 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -45,7 +45,7 @@ let package = Package( name: "SwiftUINavigationCore", dependencies: [ .product(name: "CustomDump", package: "swift-custom-dump"), - .product(name: "IssueReporting", package: "swift-issue-reporting"), + .product(name: "IssueReporting", package: "xctest-dynamic-overlay"), ] ), ], From ef8a5223764d93f8d72481c290d1570edc2bca60 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 1 Aug 2024 14:02:06 -0700 Subject: [PATCH 11/73] UIKitNavigation (#167) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * Run swift-format * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * fix * wip * wip * wip * wip * modernize wifi settings demo * wip * Run swift-format * wip * wip * added some tests * more tests * more assertions * Run swift-format * wip * uwip * Run swift-format * wip * Run swift-format * test clean up * Run swift-format * clean up tests * Run swift-format * added some tests that dont pass but should * Run swift-format * fix tests * clean up tests * Run swift-format * wip * fix some tests * wip * wip * wip * fix * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * fix * wip * wip * wip * wip * wip * wip * wip * wip * wip * fix * wip * wip * wip * wip * wip * wip * wip * wip * push trait * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * moved wifi demo into case studies * wip * wip * wip * wip * wip * case studies * wip * wip * wip * fixes * update test withUITransaction * wip * fixed some tests * fix more tests * wip * wip * wip * wip * wip * wip * remove UIKitCaseStudies target we bundle them directly into the CaseStudies target now * tvos * wip * runtime warning tests * wrote some tests, fixed some tests * fix for testDecodePath and some other tests * Update UIBindable.swift * tests for rebind and unbind * clean up and tests * wip * wip * wip * animation case study * wip * more tests and beginnings of focus case study * finish focus case study * Add accessibilityLabel property to ButtonState (#171) * Add accessibilityLabel property to ButtonState * Revert "Add accessibilityLabel property to ButtonState" This reverts commit de7a369f002793d945527447b56c4e75f7f88e39. * Add accessibilityLabel support to UIAlertController à la Stephen Celis * Make UIAlertAction convenience initializers public * Apply suggestions from code review --------- Co-authored-by: Stephen Celis * docs * wip * finish focus case study * Dismiss correct view controller when doing presentation. (#176) * Dismiss correct view controller when doing presentation. * wip * new test * Fix * wip * wip * fix project * wip * wip * wip * Fix UIKit navigation issues (#183) * Fix dismissal in `present` method * Remove unneeded params from closures * Fix push behavior when item is updated * Revert "Fix push behavior when item is updated" This reverts commit c3021c42b5e0ea0944773069fd47c352a572e219. * Add test --------- Co-authored-by: Stephen Celis * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * lots of docs * playing around with isolation * swift 6 * wip * wip * fix memory leak * let alert state compile on other platforms * wip * wip * temp * wip * Revert "temp" This reverts commit 036c93305096191f0f55ddc0d118e464fae62c1a. * wip * fixes * wip * wip * wip * fix * wip * wip * wip * fix tests * small fixes * wip; --------- Co-authored-by: stephencelis Co-authored-by: Brandon Williams Co-authored-by: mbrandonw Co-authored-by: Cosmic Flamingo <67525430+acosmicflamingo@users.noreply.github.com> Co-authored-by: Brandon Williams <135203+mbrandonw@users.noreply.github.com> Co-authored-by: Oskar Ek --- .spi.yml | 5 +- Examples/CaseStudies/01-Alerts.swift | 56 - .../CaseStudies/02-ConfirmationDialogs.swift | 53 - Examples/CaseStudies/03-Sheets.swift | 119 - Examples/CaseStudies/04-Popovers.swift | 115 - .../CaseStudies/05-FullScreenCovers.swift | 115 - .../06-NavigationDestinations.swift | 132 -- Examples/CaseStudies/07-NavigationLinks.swift | 122 - Examples/CaseStudies/08-Routing.swift | 125 - .../CaseStudies/09-CustomComponents.swift | 110 - .../CaseStudies/10-SynchronizedBindings.swift | 64 - Examples/CaseStudies/11-IfLet.swift | 48 - Examples/CaseStudies/12-IfCaseLet.swift | 55 - Examples/CaseStudies/Internal/CaseStudy.swift | 222 ++ .../CaseStudies/Internal/DetentsHelper.swift | 12 + .../{ => Internal}/FactClient.swift | 4 +- .../CaseStudies/Internal/Text+Template.swift | 59 + Examples/CaseStudies/RootView.swift | 60 +- .../SwiftUI/AlertDialogState.swift | 85 + .../CaseStudies/SwiftUI/EnumControls.swift | 61 + .../CaseStudies/SwiftUI/EnumNavigation.swift | 135 ++ .../SwiftUI/OptionalNavigation.swift | 122 + .../SwiftUI/SwiftUICaseStudies.swift | 24 + .../SwiftUI/SynchronizedBindings.swift | 62 + .../UIKit/AnimationsViewController.swift | 89 + .../BasicsNavigationViewController.swift | 115 + .../ConciseEnumNavigationViewController.swift | 131 ++ .../UIKit/EnumControlsViewController.swift | 109 + .../ErasedNavigationStackController.swift | 234 ++ .../UIKit/FocusViewController.swift | 122 + .../MinimalObservationViewController.swift | 76 + .../StaticNavigationStackController.swift | 157 ++ .../UIControlBindingsViewController.swift | 130 ++ .../CaseStudies/UIKit/UIKitCaseStudies.swift | 40 + .../WiFiFeature/ConnectToNetworkFeature.swift | 111 + .../UIKit/WiFiFeature/Network.swift | 53 + .../WiFiFeature/NetworkDetailFeature.swift | 99 + .../WiFiFeature/WiFiSettingsFeature.swift | 311 +++ .../CaseStudiesTests/CaseStudies.xctestplan | 42 + .../Internal/AssertEventually.swift | 160 ++ .../CaseStudiesTests/Internal/SetUp.swift | 18 + .../CaseStudiesTests/Internal/XCTTODO.swift | 11 + .../NavigationPathTests.swift | 646 ++++++ .../NavigationStackTests.swift | 318 +++ .../CaseStudiesTests/PresentationTests.swift | 482 ++++ .../RuntimeWarningTests.swift | 144 ++ Examples/Examples.xcodeproj/project.pbxproj | 414 +++- .../xcshareddata/swiftpm/Package.resolved | 42 +- .../xcschemes/CaseStudies.xcscheme | 96 + Examples/Inventory/Item.swift | 2 +- Makefile | 31 +- Package.resolved | 25 +- Package.swift | 50 +- Package@swift-6.0.swift | 45 +- README.md | 335 ++- Sources/SwiftNavigation/AlertState.swift | 217 ++ .../Bind.swift | 0 .../Binding.swift | 0 Sources/SwiftNavigation/ButtonState.swift | 402 ++++ .../SwiftNavigation/ButtonStateBuilder.swift | 35 + .../ConfirmationDialogState.swift | 258 +++ .../Articles/CrossPlatform.md | 34 + .../Articles/WhatIsNavigation.md | 54 + .../Extensions/AlertState.md | 6 +- .../Extensions/ButtonState.md | 14 +- .../Extensions/ConfirmationDialogState.md | 6 +- .../Extensions/TextState.md | 2 +- .../Extensions/UIBindable.md | 17 + .../Extensions/UIBinding.md | 27 + .../Documentation.docc/SwiftNavigation.md | 64 + .../HashableObject.swift | 0 .../SwiftNavigation/Internal/Exports.swift | 2 + Sources/SwiftNavigation/Observe.swift | 188 ++ .../TextState.swift | 280 ++- Sources/SwiftNavigation/UIBindable.swift | 191 ++ Sources/SwiftNavigation/UIBinding.swift | 739 ++++++ .../SwiftNavigation/UINavigationPath.swift | 205 ++ Sources/SwiftNavigation/UITransaction.swift | 94 + Sources/SwiftUINavigation/Alert.swift | 196 ++ Sources/SwiftUINavigation/Binding.swift | 57 +- .../ConfirmationDialog.swift | 158 +- .../Articles/AlertsDialogs.md | 10 +- .../Documentation.docc/Articles/Bindings.md | 3 +- .../Documentation.docc/Articles/Navigation.md | 5 +- .../Articles/SwiftUINavigationTools.md | 5 + .../Articles/WhatIsNavigation.md | 293 --- .../Extensions/Deprecations.md | 54 - .../Documentation.docc/Extensions/Switch.md | 8 - .../Documentation.docc/SwiftUINavigation.md | 105 +- .../SwiftUINavigation/FullScreenCover.swift | 4 +- .../Internal/Deprecations.swift | 2026 ----------------- .../SwiftUINavigation/Internal/Exports.swift | 3 +- .../Internal/Identified.swift | 7 +- .../Internal/LockIsolated.swift | 18 - .../NavigationDestination.swift | 19 + Sources/SwiftUINavigation/Popover.swift | 4 +- Sources/SwiftUINavigation/Sheet.swift | 4 +- Sources/SwiftUINavigation/WithState.swift | 5 + Sources/SwiftUINavigationCore/Alert.swift | 141 -- .../SwiftUINavigationCore/AlertState.swift | 284 --- .../SwiftUINavigationCore/ButtonState.swift | 377 --- .../ButtonStateBuilder.swift | 36 - .../ConfirmationDialog.swift | 150 -- .../ConfirmationDialogState.swift | 294 --- .../Extensions/AlertStateDeprecations.md | 22 - .../Extensions/ButtonStateDeprecations.md | 24 - .../ConfirmationDialogStateDeprecations.md | 21 - .../Extensions/Deprecations.md | 19 - .../SwiftUINavigationCore.md | 32 - .../Internal/Deprecations.swift | 331 --- .../NavigationDestination.swift | 25 - .../Bindings/UIColorWell.swift | 30 + .../UIKitNavigation/Bindings/UIControl.swift | 105 + .../Bindings/UIDatePicker.swift | 30 + .../Bindings/UIPageControl.swift | 28 + .../Bindings/UISegmentedControl.swift | 44 + .../UIKitNavigation/Bindings/UISlider.swift | 29 + .../UIKitNavigation/Bindings/UIStepper.swift | 29 + .../UIKitNavigation/Bindings/UISwitch.swift | 32 + .../Bindings/UITabBarController.swift | 34 + .../Bindings/UITextField.swift | 310 +++ .../Extensions/UIColorWell.md | 8 + .../Extensions/UIControlProtocol.md | 8 + .../Extensions/UIDatePicker.md | 8 + .../Extensions/UIKitAnimation.md | 35 + .../Extensions/UIPageControl.md | 8 + .../Documentation.docc/Extensions/UISlider.md | 8 + .../Extensions/UIStepper.md | 8 + .../Documentation.docc/Extensions/UISwitch.md | 8 + .../Extensions/UITextField.md | 22 + .../Extensions/UIViewController.md | 26 + .../Documentation.docc/Extensions/observe.md | 7 + .../Documentation.docc/UIKitNavigation.md | 165 ++ .../Internal/ErrorMechanism.swift | 20 + .../UIKitNavigation/Internal/Exports.swift | 3 + .../Internal/PopFromViewController.swift | 13 + .../Internal/ToOptionalUnit.swift | 12 + .../UIKitNavigation/Navigation/Dismiss.swift | 45 + .../NavigationStackController.swift | 443 ++++ .../Navigation/Presentation.swift | 452 ++++ Sources/UIKitNavigation/Navigation/Push.swift | 43 + .../Navigation/UIAlertController.swift | 87 + Sources/UIKitNavigation/Observe.swift | 177 ++ .../SwiftUI/Representable.swift | 24 + Sources/UIKitNavigation/UIBinding.swift | 13 + Sources/UIKitNavigation/UIKitAnimation.swift | 408 ++++ Sources/UIKitNavigation/UITransaction.swift | 51 + Sources/UIKitNavigationShim/include/shim.h | 19 + Sources/UIKitNavigationShim/shim.m | 88 + .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcschemes/SwiftNavigation.xcscheme | 78 + .../xcschemes/SwiftUINavigation.xcscheme | 25 +- .../xcschemes/SwiftUINavigationCore.xcscheme | 67 + .../xcschemes/UIKitNavigation.xcscheme | 72 + .../xcshareddata/swiftpm/Package.resolved | 122 - Tests/SwiftNavigation.xctestplan | 24 + .../SwiftNavigationTests/IsolationTests.swift | 50 + .../SwiftNavigationTests/LifetimeTests.swift | 33 + Tests/SwiftUINavigation.xctestplan | 24 + Tests/SwiftUINavigationTests/AlertTests.swift | 123 +- .../SwiftUINavigationTests/BindingTests.swift | 3 +- .../ButtonStateTests.swift | 33 +- .../SwiftUINavigationTests.swift | 16 +- .../TextStateTests.swift | 2 +- Tests/UIKitNavigation.xctestplan | 24 + .../Internal/XCTTODO.swift | 11 + .../MemoryManagementTests.swift | 69 + .../UIBindableTests.swift | 49 + .../UIKitNavigationTests/UIBindingTests.swift | 187 ++ .../UIKitNavigationTests/UIControlTests.swift | 176 ++ .../UINavigationPathTests.swift | 32 + 172 files changed, 12345 insertions(+), 5933 deletions(-) delete mode 100644 Examples/CaseStudies/01-Alerts.swift delete mode 100644 Examples/CaseStudies/02-ConfirmationDialogs.swift delete mode 100644 Examples/CaseStudies/03-Sheets.swift delete mode 100644 Examples/CaseStudies/04-Popovers.swift delete mode 100644 Examples/CaseStudies/05-FullScreenCovers.swift delete mode 100644 Examples/CaseStudies/06-NavigationDestinations.swift delete mode 100644 Examples/CaseStudies/07-NavigationLinks.swift delete mode 100644 Examples/CaseStudies/08-Routing.swift delete mode 100644 Examples/CaseStudies/09-CustomComponents.swift delete mode 100644 Examples/CaseStudies/10-SynchronizedBindings.swift delete mode 100644 Examples/CaseStudies/11-IfLet.swift delete mode 100644 Examples/CaseStudies/12-IfCaseLet.swift create mode 100644 Examples/CaseStudies/Internal/CaseStudy.swift create mode 100644 Examples/CaseStudies/Internal/DetentsHelper.swift rename Examples/CaseStudies/{ => Internal}/FactClient.swift (90%) create mode 100644 Examples/CaseStudies/Internal/Text+Template.swift create mode 100644 Examples/CaseStudies/SwiftUI/AlertDialogState.swift create mode 100644 Examples/CaseStudies/SwiftUI/EnumControls.swift create mode 100644 Examples/CaseStudies/SwiftUI/EnumNavigation.swift create mode 100644 Examples/CaseStudies/SwiftUI/OptionalNavigation.swift create mode 100644 Examples/CaseStudies/SwiftUI/SwiftUICaseStudies.swift create mode 100644 Examples/CaseStudies/SwiftUI/SynchronizedBindings.swift create mode 100644 Examples/CaseStudies/UIKit/AnimationsViewController.swift create mode 100644 Examples/CaseStudies/UIKit/BasicsNavigationViewController.swift create mode 100644 Examples/CaseStudies/UIKit/ConciseEnumNavigationViewController.swift create mode 100644 Examples/CaseStudies/UIKit/EnumControlsViewController.swift create mode 100644 Examples/CaseStudies/UIKit/ErasedNavigationStackController.swift create mode 100644 Examples/CaseStudies/UIKit/FocusViewController.swift create mode 100644 Examples/CaseStudies/UIKit/MinimalObservationViewController.swift create mode 100644 Examples/CaseStudies/UIKit/StaticNavigationStackController.swift create mode 100644 Examples/CaseStudies/UIKit/UIControlBindingsViewController.swift create mode 100644 Examples/CaseStudies/UIKit/UIKitCaseStudies.swift create mode 100644 Examples/CaseStudies/UIKit/WiFiFeature/ConnectToNetworkFeature.swift create mode 100644 Examples/CaseStudies/UIKit/WiFiFeature/Network.swift create mode 100644 Examples/CaseStudies/UIKit/WiFiFeature/NetworkDetailFeature.swift create mode 100644 Examples/CaseStudies/UIKit/WiFiFeature/WiFiSettingsFeature.swift create mode 100644 Examples/CaseStudiesTests/CaseStudies.xctestplan create mode 100644 Examples/CaseStudiesTests/Internal/AssertEventually.swift create mode 100644 Examples/CaseStudiesTests/Internal/SetUp.swift create mode 100644 Examples/CaseStudiesTests/Internal/XCTTODO.swift create mode 100644 Examples/CaseStudiesTests/NavigationPathTests.swift create mode 100644 Examples/CaseStudiesTests/NavigationStackTests.swift create mode 100644 Examples/CaseStudiesTests/PresentationTests.swift create mode 100644 Examples/CaseStudiesTests/RuntimeWarningTests.swift create mode 100644 Examples/Examples.xcodeproj/xcshareddata/xcschemes/CaseStudies.xcscheme create mode 100644 Sources/SwiftNavigation/AlertState.swift rename Sources/{SwiftUINavigationCore => SwiftNavigation}/Bind.swift (100%) rename Sources/{SwiftUINavigationCore => SwiftNavigation}/Binding.swift (100%) create mode 100644 Sources/SwiftNavigation/ButtonState.swift create mode 100644 Sources/SwiftNavigation/ButtonStateBuilder.swift create mode 100644 Sources/SwiftNavigation/ConfirmationDialogState.swift create mode 100644 Sources/SwiftNavigation/Documentation.docc/Articles/CrossPlatform.md create mode 100644 Sources/SwiftNavigation/Documentation.docc/Articles/WhatIsNavigation.md rename Sources/{SwiftUINavigationCore => SwiftNavigation}/Documentation.docc/Extensions/AlertState.md (67%) rename Sources/{SwiftUINavigationCore => SwiftNavigation}/Documentation.docc/Extensions/ButtonState.md (59%) rename Sources/{SwiftUINavigationCore => SwiftNavigation}/Documentation.docc/Extensions/ConfirmationDialogState.md (74%) rename Sources/{SwiftUINavigationCore => SwiftNavigation}/Documentation.docc/Extensions/TextState.md (82%) create mode 100644 Sources/SwiftNavigation/Documentation.docc/Extensions/UIBindable.md create mode 100644 Sources/SwiftNavigation/Documentation.docc/Extensions/UIBinding.md create mode 100644 Sources/SwiftNavigation/Documentation.docc/SwiftNavigation.md rename Sources/{SwiftUINavigation => SwiftNavigation}/HashableObject.swift (100%) create mode 100644 Sources/SwiftNavigation/Internal/Exports.swift create mode 100644 Sources/SwiftNavigation/Observe.swift rename Sources/{SwiftUINavigationCore => SwiftNavigation}/TextState.swift (80%) create mode 100644 Sources/SwiftNavigation/UIBindable.swift create mode 100644 Sources/SwiftNavigation/UIBinding.swift create mode 100644 Sources/SwiftNavigation/UINavigationPath.swift create mode 100644 Sources/SwiftNavigation/UITransaction.swift create mode 100644 Sources/SwiftUINavigation/Documentation.docc/Articles/SwiftUINavigationTools.md delete mode 100644 Sources/SwiftUINavigation/Documentation.docc/Articles/WhatIsNavigation.md delete mode 100644 Sources/SwiftUINavigation/Documentation.docc/Extensions/Deprecations.md delete mode 100644 Sources/SwiftUINavigation/Documentation.docc/Extensions/Switch.md delete mode 100644 Sources/SwiftUINavigation/Internal/Deprecations.swift delete mode 100644 Sources/SwiftUINavigation/Internal/LockIsolated.swift delete mode 100644 Sources/SwiftUINavigationCore/Alert.swift delete mode 100644 Sources/SwiftUINavigationCore/AlertState.swift delete mode 100644 Sources/SwiftUINavigationCore/ButtonState.swift delete mode 100644 Sources/SwiftUINavigationCore/ButtonStateBuilder.swift delete mode 100644 Sources/SwiftUINavigationCore/ConfirmationDialog.swift delete mode 100644 Sources/SwiftUINavigationCore/ConfirmationDialogState.swift delete mode 100644 Sources/SwiftUINavigationCore/Documentation.docc/Extensions/AlertStateDeprecations.md delete mode 100644 Sources/SwiftUINavigationCore/Documentation.docc/Extensions/ButtonStateDeprecations.md delete mode 100644 Sources/SwiftUINavigationCore/Documentation.docc/Extensions/ConfirmationDialogStateDeprecations.md delete mode 100644 Sources/SwiftUINavigationCore/Documentation.docc/Extensions/Deprecations.md delete mode 100644 Sources/SwiftUINavigationCore/Documentation.docc/SwiftUINavigationCore.md delete mode 100644 Sources/SwiftUINavigationCore/Internal/Deprecations.swift delete mode 100644 Sources/SwiftUINavigationCore/NavigationDestination.swift create mode 100644 Sources/UIKitNavigation/Bindings/UIColorWell.swift create mode 100644 Sources/UIKitNavigation/Bindings/UIControl.swift create mode 100644 Sources/UIKitNavigation/Bindings/UIDatePicker.swift create mode 100644 Sources/UIKitNavigation/Bindings/UIPageControl.swift create mode 100644 Sources/UIKitNavigation/Bindings/UISegmentedControl.swift create mode 100644 Sources/UIKitNavigation/Bindings/UISlider.swift create mode 100644 Sources/UIKitNavigation/Bindings/UIStepper.swift create mode 100644 Sources/UIKitNavigation/Bindings/UISwitch.swift create mode 100644 Sources/UIKitNavigation/Bindings/UITabBarController.swift create mode 100644 Sources/UIKitNavigation/Bindings/UITextField.swift create mode 100644 Sources/UIKitNavigation/Documentation.docc/Extensions/UIColorWell.md create mode 100644 Sources/UIKitNavigation/Documentation.docc/Extensions/UIControlProtocol.md create mode 100644 Sources/UIKitNavigation/Documentation.docc/Extensions/UIDatePicker.md create mode 100644 Sources/UIKitNavigation/Documentation.docc/Extensions/UIKitAnimation.md create mode 100644 Sources/UIKitNavigation/Documentation.docc/Extensions/UIPageControl.md create mode 100644 Sources/UIKitNavigation/Documentation.docc/Extensions/UISlider.md create mode 100644 Sources/UIKitNavigation/Documentation.docc/Extensions/UIStepper.md create mode 100644 Sources/UIKitNavigation/Documentation.docc/Extensions/UISwitch.md create mode 100644 Sources/UIKitNavigation/Documentation.docc/Extensions/UITextField.md create mode 100644 Sources/UIKitNavigation/Documentation.docc/Extensions/UIViewController.md create mode 100644 Sources/UIKitNavigation/Documentation.docc/Extensions/observe.md create mode 100644 Sources/UIKitNavigation/Documentation.docc/UIKitNavigation.md create mode 100644 Sources/UIKitNavigation/Internal/ErrorMechanism.swift create mode 100644 Sources/UIKitNavigation/Internal/Exports.swift create mode 100644 Sources/UIKitNavigation/Internal/PopFromViewController.swift create mode 100644 Sources/UIKitNavigation/Internal/ToOptionalUnit.swift create mode 100644 Sources/UIKitNavigation/Navigation/Dismiss.swift create mode 100644 Sources/UIKitNavigation/Navigation/NavigationStackController.swift create mode 100644 Sources/UIKitNavigation/Navigation/Presentation.swift create mode 100644 Sources/UIKitNavigation/Navigation/Push.swift create mode 100644 Sources/UIKitNavigation/Navigation/UIAlertController.swift create mode 100644 Sources/UIKitNavigation/Observe.swift create mode 100644 Sources/UIKitNavigation/SwiftUI/Representable.swift create mode 100644 Sources/UIKitNavigation/UIBinding.swift create mode 100644 Sources/UIKitNavigation/UIKitAnimation.swift create mode 100644 Sources/UIKitNavigation/UITransaction.swift create mode 100644 Sources/UIKitNavigationShim/include/shim.h create mode 100644 Sources/UIKitNavigationShim/shim.m rename {SwiftUINavigation.xcworkspace => SwiftNavigation.xcworkspace}/contents.xcworkspacedata (100%) rename {SwiftUINavigation.xcworkspace => SwiftNavigation.xcworkspace}/xcshareddata/IDEWorkspaceChecks.plist (100%) create mode 100644 SwiftNavigation.xcworkspace/xcshareddata/xcschemes/SwiftNavigation.xcscheme rename {SwiftUINavigation.xcworkspace => SwiftNavigation.xcworkspace}/xcshareddata/xcschemes/SwiftUINavigation.xcscheme (80%) create mode 100644 SwiftNavigation.xcworkspace/xcshareddata/xcschemes/SwiftUINavigationCore.xcscheme create mode 100644 SwiftNavigation.xcworkspace/xcshareddata/xcschemes/UIKitNavigation.xcscheme delete mode 100644 SwiftUINavigation.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 Tests/SwiftNavigation.xctestplan create mode 100644 Tests/SwiftNavigationTests/IsolationTests.swift create mode 100644 Tests/SwiftNavigationTests/LifetimeTests.swift create mode 100644 Tests/SwiftUINavigation.xctestplan create mode 100644 Tests/UIKitNavigation.xctestplan create mode 100644 Tests/UIKitNavigationTests/Internal/XCTTODO.swift create mode 100644 Tests/UIKitNavigationTests/MemoryManagementTests.swift create mode 100644 Tests/UIKitNavigationTests/UIBindableTests.swift create mode 100644 Tests/UIKitNavigationTests/UIBindingTests.swift create mode 100644 Tests/UIKitNavigationTests/UIControlTests.swift create mode 100644 Tests/UIKitNavigationTests/UINavigationPathTests.swift diff --git a/.spi.yml b/.spi.yml index eeeb1f4d0..11f61019a 100644 --- a/.spi.yml +++ b/.spi.yml @@ -1,4 +1,7 @@ version: 1 builder: configs: - - documentation_targets: [SwiftUINavigation, SwiftUINavigationCore] + - documentation_targets: + - SwiftNavigation + - SwiftUINavigation + - UIKitNavigation diff --git a/Examples/CaseStudies/01-Alerts.swift b/Examples/CaseStudies/01-Alerts.swift deleted file mode 100644 index 9f91a46aa..000000000 --- a/Examples/CaseStudies/01-Alerts.swift +++ /dev/null @@ -1,56 +0,0 @@ -import SwiftUI -import SwiftUINavigation - -@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) -struct OptionalAlerts: View { - @State private var model = FeatureModel() - - var body: some View { - List { - Stepper("Number: \(model.count)", value: $model.count) - Button { - Task { await model.numberFactButtonTapped() } - } label: { - HStack { - Text("Get number fact") - if model.isLoading { - Spacer() - ProgressView() - } - } - } - .disabled(model.isLoading) - } - .alert(item: $model.fact) { - Text("Fact about \($0.number)") - } actions: { - Button("Get another fact about \($0.number)") { - Task { await model.numberFactButtonTapped() } - } - Button("Close", role: .cancel) { - model.fact = nil - } - } message: { - Text($0.description) - } - .navigationTitle("Alerts") - } -} - -@Observable -private class FeatureModel { - var count = 0 - var isLoading = false - var fact: Fact? - - @MainActor - func numberFactButtonTapped() async { - isLoading = true - defer { isLoading = false } - fact = await getNumberFact(count) - } -} - -#Preview { - OptionalAlerts() -} diff --git a/Examples/CaseStudies/02-ConfirmationDialogs.swift b/Examples/CaseStudies/02-ConfirmationDialogs.swift deleted file mode 100644 index 1c7c8e72d..000000000 --- a/Examples/CaseStudies/02-ConfirmationDialogs.swift +++ /dev/null @@ -1,53 +0,0 @@ -import SwiftUI -import SwiftUINavigation - -@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) -struct OptionalConfirmationDialogs: View { - @State private var model = FeatureModel() - - var body: some View { - List { - Stepper("Number: \(model.count)", value: $model.count) - Button { - Task { await model.numberFactButtonTapped() } - } label: { - HStack { - Text("Get number fact") - if model.isLoading { - Spacer() - ProgressView() - } - } - } - .disabled(model.isLoading) - .confirmationDialog(item: $model.fact, titleVisibility: .visible) { - Text("Fact about \($0.number)") - } actions: { - Button("Get another fact about \($0.number)") { - Task { await model.numberFactButtonTapped() } - } - } message: { - Text($0.description) - } - } - .navigationTitle("Dialogs") - } -} - -@Observable -private class FeatureModel { - var count = 0 - var isLoading = false - var fact: Fact? - - @MainActor - func numberFactButtonTapped() async { - isLoading = true - defer { isLoading = false } - fact = await getNumberFact(count) - } -} - -#Preview { - OptionalConfirmationDialogs() -} diff --git a/Examples/CaseStudies/03-Sheets.swift b/Examples/CaseStudies/03-Sheets.swift deleted file mode 100644 index f1d29169f..000000000 --- a/Examples/CaseStudies/03-Sheets.swift +++ /dev/null @@ -1,119 +0,0 @@ -import SwiftUI -import SwiftUINavigation - -struct OptionalSheets: View { - @State private var model = FeatureModel() - - var body: some View { - List { - Section { - Stepper("Number: \(model.count)", value: $model.count) - - HStack { - Button("Get number fact") { - Task { await model.numberFactButtonTapped() } - } - - if model.isLoading { - Spacer() - ProgressView() - } - } - } header: { - Text("Fact Finder") - } - - Section { - ForEach(model.savedFacts) { fact in - Text(fact.description) - } - .onDelete { model.removeSavedFacts(atOffsets: $0) } - } header: { - Text("Saved Facts") - } - } - .sheet(item: $model.fact) { $fact in - NavigationStack { - FactEditor(fact: $fact.description) - .disabled(model.isLoading) - .foregroundColor(model.isLoading ? .gray : nil) - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button("Cancel") { - model.cancelButtonTapped() - } - } - ToolbarItem(placement: .confirmationAction) { - Button("Save") { - model.saveButtonTapped(fact: fact) - } - } - } - } - } - .navigationTitle("Sheets") - } -} - -private struct FactEditor: View { - @Binding var fact: String - - var body: some View { - VStack { - TextEditor(text: $fact) - } - .padding() - .navigationTitle("Fact editor") - } -} - -@Observable -private class FeatureModel { - var count = 0 - var fact: Fact? - var isLoading = false - var savedFacts: [Fact] = [] - private var task: Task? - - deinit { - task?.cancel() - } - - @MainActor - func numberFactButtonTapped() async { - isLoading = true - fact = Fact(description: "\(count) is still loading...", number: count) - task = Task { - let fact = await getNumberFact(self.count) - isLoading = false - guard !Task.isCancelled - else { return } - self.fact = fact - } - await task?.value - } - - @MainActor - func cancelButtonTapped() { - task?.cancel() - task = nil - fact = nil - } - - @MainActor - func saveButtonTapped(fact: Fact) { - task?.cancel() - task = nil - savedFacts.append(fact) - self.fact = nil - } - - @MainActor - func removeSavedFacts(atOffsets offsets: IndexSet) { - savedFacts.remove(atOffsets: offsets) - } -} - -#Preview { - OptionalSheets() -} diff --git a/Examples/CaseStudies/04-Popovers.swift b/Examples/CaseStudies/04-Popovers.swift deleted file mode 100644 index 292816baf..000000000 --- a/Examples/CaseStudies/04-Popovers.swift +++ /dev/null @@ -1,115 +0,0 @@ -import SwiftUI -import SwiftUINavigation - -struct OptionalPopovers: View { - @State private var model = FeatureModel() - - var body: some View { - List { - Section { - Stepper("Number: \(model.count)", value: $model.count) - - HStack { - Button("Get number fact") { - Task { await model.numberFactButtonTapped() } - } - .popover(item: $model.fact, arrowEdge: .bottom) { $fact in - NavigationStack { - FactEditor(fact: $fact.description) - .disabled(model.isLoading) - .foregroundColor(model.isLoading ? .gray : nil) - .navigationBarItems( - leading: Button("Cancel") { - model.cancelButtonTapped() - }, - trailing: Button("Save") { - model.saveButtonTapped(fact: fact) - } - ) - } - } - - if model.isLoading { - Spacer() - ProgressView() - } - } - } header: { - Text("Fact Finder") - } - - Section { - ForEach(model.savedFacts) { fact in - Text(fact.description) - } - .onDelete { model.removeSavedFacts(atOffsets: $0) } - } header: { - Text("Saved Facts") - } - } - .navigationTitle("Popovers") - } -} - -private struct FactEditor: View { - @Binding var fact: String - - var body: some View { - VStack { - TextEditor(text: $fact) - } - .padding() - .navigationTitle("Fact editor") - } -} - -@Observable -private class FeatureModel { - var count = 0 - var fact: Fact? - var isLoading = false - var savedFacts: [Fact] = [] - private var task: Task? - - deinit { - self.task?.cancel() - } - - @MainActor - func numberFactButtonTapped() async { - isLoading = true - fact = Fact(description: "\(count) is still loading...", number: count) - task = Task { - let fact = await getNumberFact(self.count) - isLoading = false - guard !Task.isCancelled - else { return } - self.fact = fact - } - await task?.value - } - - @MainActor - func cancelButtonTapped() { - task?.cancel() - task = nil - fact = nil - } - - @MainActor - func saveButtonTapped(fact: Fact) { - task?.cancel() - task = nil - savedFacts.append(fact) - self.fact = nil - } - - @MainActor - func removeSavedFacts(atOffsets offsets: IndexSet) { - savedFacts.remove(atOffsets: offsets) - } -} - -#Preview { - OptionalPopovers() -} diff --git a/Examples/CaseStudies/05-FullScreenCovers.swift b/Examples/CaseStudies/05-FullScreenCovers.swift deleted file mode 100644 index cb33d8f61..000000000 --- a/Examples/CaseStudies/05-FullScreenCovers.swift +++ /dev/null @@ -1,115 +0,0 @@ -import SwiftUI -import SwiftUINavigation - -struct OptionalFullScreenCovers: View { - @State private var model = FeatureModel() - - var body: some View { - List { - Section { - Stepper("Number: \(model.count)", value: $model.count) - - HStack { - Button("Get number fact") { - Task { await model.numberFactButtonTapped() } - } - - if model.isLoading { - Spacer() - ProgressView() - } - } - } header: { - Text("Fact Finder") - } - - Section { - ForEach(model.savedFacts) { fact in - Text(fact.description) - } - .onDelete { model.removeSavedFacts(atOffsets: $0) } - } header: { - Text("Saved Facts") - } - } - .fullScreenCover(item: $model.fact) { $fact in - NavigationStack { - FactEditor(fact: $fact.description) - .disabled(model.isLoading) - .foregroundColor(model.isLoading ? .gray : nil) - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button("Cancel") { - model.cancelButtonTapped() - } - } - ToolbarItem(placement: .confirmationAction) { - Button("Save") { - model.saveButtonTapped(fact: fact) - } - } - } - } - } - .navigationTitle("Full-screen covers") - } -} - -private struct FactEditor: View { - @Binding var fact: String - - var body: some View { - VStack { - TextEditor(text: $fact) - } - .padding() - .navigationTitle("Fact editor") - } -} - -@Observable -private class FeatureModel { - var count = 0 - var fact: Fact? - var isLoading = false - var savedFacts: [Fact] = [] - private var task: Task? - - @MainActor - func numberFactButtonTapped() async { - isLoading = true - fact = Fact(description: "\(count) is still loading...", number: count) - task = Task { - let fact = await getNumberFact(count) - isLoading = false - guard !Task.isCancelled - else { return } - self.fact = fact - } - await task?.value - } - - @MainActor - func cancelButtonTapped() { - task?.cancel() - task = nil - fact = nil - } - - @MainActor - func saveButtonTapped(fact: Fact) { - task?.cancel() - task = nil - savedFacts.append(fact) - self.fact = nil - } - - @MainActor - func removeSavedFacts(atOffsets offsets: IndexSet) { - savedFacts.remove(atOffsets: offsets) - } -} - -#Preview { - OptionalFullScreenCovers() -} diff --git a/Examples/CaseStudies/06-NavigationDestinations.swift b/Examples/CaseStudies/06-NavigationDestinations.swift deleted file mode 100644 index 33dd0a43b..000000000 --- a/Examples/CaseStudies/06-NavigationDestinations.swift +++ /dev/null @@ -1,132 +0,0 @@ -import SwiftUI -import SwiftUINavigation - -@available(iOS 16, *) -struct NavigationDestinations: View { - @State private var model = FeatureModel() - - var body: some View { - List { - Section { - Stepper("Number: \(model.count)", value: $model.count) - - HStack { - Button("Get number fact") { - Task { await model.numberFactButtonTapped() } - } - - if model.isLoading { - Spacer() - ProgressView() - } - } - } header: { - Text("Fact Finder") - } - - Section { - ForEach(model.savedFacts) { fact in - Text(fact.description) - } - .onDelete { model.removeSavedFacts(atOffsets: $0) } - } header: { - Text("Saved Facts") - } - } - .navigationTitle("Destinations") - .navigationDestination(item: $model.fact) { $fact in - FactEditor(fact: $fact.description) - .disabled(model.isLoading) - .foregroundColor(model.isLoading ? .gray : nil) - .navigationBarBackButtonHidden(true) - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button("Cancel") { - Task { await model.cancelButtonTapped() } - } - } - ToolbarItem(placement: .confirmationAction) { - Button("Save") { - Task { await model.saveButtonTapped(fact: fact) } - } - } - } - } - } -} - -private struct FactEditor: View { - @Binding var fact: String - - var body: some View { - VStack { - if #available(iOS 14, *) { - TextEditor(text: $fact) - } else { - TextField("Untitled", text: $fact) - } - } - .padding() - .navigationBarTitle("Fact Editor") - } -} - -@Observable -private class FeatureModel { - var count = 0 - var fact: Fact? - var isLoading = false - var savedFacts: [Fact] = [] - private var task: Task? - - deinit { - task?.cancel() - } - - @MainActor - func setFactNavigation(isActive: Bool) async { - if isActive { - isLoading = true - fact = Fact(description: "\(count) is still loading...", number: count) - task = Task { - let fact = await getNumberFact(self.count) - isLoading = false - guard !Task.isCancelled - else { return } - self.fact = fact - } - await task?.value - } else { - task?.cancel() - task = nil - fact = nil - } - } - - @MainActor - func numberFactButtonTapped() async { - await setFactNavigation(isActive: true) - } - - @MainActor - func cancelButtonTapped() async { - await setFactNavigation(isActive: false) - } - - @MainActor - func saveButtonTapped(fact: Fact) async { - savedFacts.append(fact) - await setFactNavigation(isActive: false) - } - - @MainActor - func removeSavedFacts(atOffsets offsets: IndexSet) { - savedFacts.remove(atOffsets: offsets) - } -} - -#Preview { - NavigationStack { - NavigationDestinations() - } -} diff --git a/Examples/CaseStudies/07-NavigationLinks.swift b/Examples/CaseStudies/07-NavigationLinks.swift deleted file mode 100644 index 4b87b6b05..000000000 --- a/Examples/CaseStudies/07-NavigationLinks.swift +++ /dev/null @@ -1,122 +0,0 @@ -import SwiftUI -import SwiftUINavigation - -struct OptionalNavigationLinks: View { - @State private var model = FeatureModel() - - var body: some View { - List { - Section { - Stepper("Number: \(model.count)", value: $model.count) - - HStack { - Button("Get number fact") { - Task { await model.setFactNavigation(isActive: true) } - } - - if self.model.isLoading { - Spacer() - ProgressView() - } - } - } header: { - Text("Fact Finder") - } - - Section { - ForEach(model.savedFacts) { fact in - Text(fact.description) - } - .onDelete { model.removeSavedFacts(atOffsets: $0) } - } header: { - Text("Saved Facts") - } - } - .navigationDestination(item: $model.fact) { $fact in - FactEditor(fact: $fact.description) - .disabled(model.isLoading) - .foregroundColor(model.isLoading ? .gray : nil) - .navigationBarBackButtonHidden(true) - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button("Cancel") { - Task { await model.cancelButtonTapped() } - } - } - ToolbarItem(placement: .confirmationAction) { - Button("Save") { - Task { await model.saveButtonTapped(fact: fact) } - } - } - } - } - .navigationTitle("Links") - } -} - -private struct FactEditor: View { - @Binding var fact: String - - var body: some View { - VStack { - TextEditor(text: $fact) - } - .padding() - .navigationTitle("Fact editor") - } -} - -@Observable -private class FeatureModel { - var count = 0 - var fact: Fact? - var isLoading = false - var savedFacts: [Fact] = [] - private var task: Task? - - deinit { - task?.cancel() - } - - @MainActor - func setFactNavigation(isActive: Bool) async { - if isActive { - isLoading = true - fact = Fact(description: "\(count) is still loading...", number: count) - task = Task { - let fact = await getNumberFact(self.count) - isLoading = false - guard !Task.isCancelled - else { return } - self.fact = fact - } - await task?.value - } else { - task?.cancel() - task = nil - fact = nil - } - } - - @MainActor - func cancelButtonTapped() async { - await setFactNavigation(isActive: false) - } - - @MainActor - func saveButtonTapped(fact: Fact) async { - savedFacts.append(fact) - await setFactNavigation(isActive: false) - } - - @MainActor - func removeSavedFacts(atOffsets offsets: IndexSet) { - savedFacts.remove(atOffsets: offsets) - } -} - -#Preview { - NavigationStack { - OptionalNavigationLinks() - } -} diff --git a/Examples/CaseStudies/08-Routing.swift b/Examples/CaseStudies/08-Routing.swift deleted file mode 100644 index 6c34662a6..000000000 --- a/Examples/CaseStudies/08-Routing.swift +++ /dev/null @@ -1,125 +0,0 @@ -import SwiftUI -import SwiftUINavigation - -private let readMe = """ - This case study demonstrates how to power multiple forms of navigation from a single destination \ - enum that describes all of the possible destinations one can travel to from this screen. - - The screen has four navigation destinations: an alert, a confirmation dialog, a navigation link \ - to a count stepper, and a modal sheet to a count stepper. The state for each of these \ - destinations is held as associated data of an enum, and bindings to the cases of that enum are \ - derived using the tools in this library. - """ - -@CasePathable -enum Destination { - case alert(AlertState) - case confirmationDialog(ConfirmationDialogState) - case link(Int) - case sheet(Int) - - enum AlertAction { - case randomize - case reset - } - enum DialogAction { - case decrement - case increment - } -} - -struct Routing: View { - @State var count = 0 - @State var destination: Destination? - - var body: some View { - Form { - Section { - Text(readMe) - } - - Section { - Text("Count: \(count)") - } - - Button("Alert") { - destination = .alert( - AlertState { - TextState("Update count?") - } actions: { - ButtonState(action: .send(.randomize)) { - TextState("Randomize") - } - ButtonState(role: .destructive, action: .send(.reset)) { - TextState("Reset") - } - } - ) - } - - Button("Confirmation dialog") { - destination = .confirmationDialog( - ConfirmationDialogState(titleVisibility: .visible) { - TextState("Update count?") - } actions: { - ButtonState(action: .send(.increment)) { - TextState("Increment") - } - ButtonState(action: .send(.decrement)) { - TextState("Decrement") - } - } - ) - } - - Button("Link") { - destination = .link(count) - } - - Button("Sheet") { - destination = .sheet(count) - } - } - .navigationTitle("Routing") - .alert($destination.alert) { action in - switch action { - case .randomize?: - count = .random(in: 0...1_000) - case .reset?: - count = 0 - case nil: - break - } - } - .confirmationDialog($destination.confirmationDialog) { action in - switch action { - case .decrement?: - count -= 1 - case .increment?: - count += 1 - case nil: - break - } - } - .navigationDestination(item: $destination.link) { $count in - Form { - Stepper("Count: \(count)", value: $count) - } - .navigationTitle("Routing link") - } - .sheet(item: $destination.sheet, id: \.self) { $count in - NavigationStack { - Form { - Stepper("Count: \(count)", value: $count) - } - .navigationTitle("Routing sheet") - } - } - } -} - -#Preview { - NavigationStack { - Routing() - } -} diff --git a/Examples/CaseStudies/09-CustomComponents.swift b/Examples/CaseStudies/09-CustomComponents.swift deleted file mode 100644 index 0533c2222..000000000 --- a/Examples/CaseStudies/09-CustomComponents.swift +++ /dev/null @@ -1,110 +0,0 @@ -import SwiftUI -import SwiftUINavigation - -private let readMe = """ - This case study demonstrates how to enhance an existing SwiftUI component so that it can be \ - driven off of optional and enum state. - - The BottomMenuModifier component in this is file is primarily powered by a simple boolean \ - binding, which means its content cannot be dynamic based off of the source of truth that drives \ - its presentation, and it cannot make mutations to the source of truth. - - However, by leveraging the binding transformations that come with this library we can extend the \ - bottom menu component with additional APIs that allow presentation and dismissal to be powered \ - by optionals and enums. - """ - -struct CustomComponents: View { - @State var count: Int? - - var body: some View { - Form { - Section { - Text(readMe) - } - - Button("Show bottom menu") { - withAnimation { - count = 0 - } - } - - if let count = count, count > 0 { - Text("Current count: \(count)") - .transition(.opacity) - } - } - .bottomMenu(item: $count) { $count in - Stepper("Number: \(count)", value: $count.animation()) - } - .navigationTitle("Custom components") - } -} - -private struct BottomMenuModifier: ViewModifier -where BottomMenuContent: View { - @Binding var isActive: Bool - let content: () -> BottomMenuContent - - func body(content: Content) -> some View { - content.overlay( - ZStack(alignment: .bottom) { - if isActive { - Rectangle() - .fill(Color.black.opacity(0.4)) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .onTapGesture { - withAnimation { - isActive = false - } - } - .zIndex(1) - .transition(.opacity) - - self.content() - .padding() - .background(Color.white) - .cornerRadius(10) - .frame(maxWidth: .infinity) - .padding(24) - .padding(.bottom) - .zIndex(2) - .transition(.move(edge: .bottom)) - } - } - .ignoresSafeArea() - ) - } -} - -extension View { - fileprivate func bottomMenu( - isActive: Binding, - @ViewBuilder content: @escaping () -> Content - ) -> some View - where Content: View { - modifier( - BottomMenuModifier( - isActive: isActive, - content: content - ) - ) - } - - fileprivate func bottomMenu( - item: Binding, - @ViewBuilder content: @escaping (Binding) -> Content - ) -> some View - where Content: View { - modifier( - BottomMenuModifier( - isActive: Binding(item), - content: { Binding(unwrapping: item).map(content) } - ) - ) - } -} - -#Preview { - CustomComponents() -} diff --git a/Examples/CaseStudies/10-SynchronizedBindings.swift b/Examples/CaseStudies/10-SynchronizedBindings.swift deleted file mode 100644 index c8abcaf13..000000000 --- a/Examples/CaseStudies/10-SynchronizedBindings.swift +++ /dev/null @@ -1,64 +0,0 @@ -import SwiftUI -import SwiftUINavigation - -private let readMe = """ - This demonstrates how to synchronize model state with view state using the "bind" view modifier. \ - The model starts focused on the "Username" field, which is immediately focused when the form \ - first appears. When you tap the "Sign in" button, the focus will change to the first non-empty \ - field. - """ - -struct SynchronizedBindings: View { - @FocusState private var focusedField: FeatureModel.Field? - @State private var model = FeatureModel() - - var body: some View { - Form { - Section { - Text(readMe) - } - - Section { - TextField("Username", text: $model.username) - .focused($focusedField, equals: .username) - - SecureField("Password", text: $model.password) - .focused($focusedField, equals: .password) - - Button("Sign In") { - model.signInButtonTapped() - } - .buttonStyle(.borderedProminent) - } - .textFieldStyle(.roundedBorder) - } - .bind($model.focusedField, to: $focusedField) - .navigationTitle("Synchronized focus") - } -} - -@Observable -private class FeatureModel { - enum Field: String { - case username - case password - } - - var focusedField: Field? = .username - var password: String = "" - var username: String = "" - - func signInButtonTapped() { - if username.isEmpty { - focusedField = .username - } else if password.isEmpty { - focusedField = .password - } else { - focusedField = nil - } - } -} - -#Preview { - SynchronizedBindings() -} diff --git a/Examples/CaseStudies/11-IfLet.swift b/Examples/CaseStudies/11-IfLet.swift deleted file mode 100644 index f6ec2cd4e..000000000 --- a/Examples/CaseStudies/11-IfLet.swift +++ /dev/null @@ -1,48 +0,0 @@ -import SwiftUI -import SwiftUINavigation - -private let readMe = """ - This demonstrates how to unwrap a binding of an optional into a binding of an honest value. - - Tap the "Edit" button to put the form into edit mode. Then you can make changes to the message \ - and either commit the changes by tapping "Save", or discard the changes by tapping "Discard". - """ - -struct IfLetCaseStudy: View { - @State var string: String = "Hello" - @State var editableString: String? - - var body: some View { - Form { - Section { - Text(readMe) - } - Binding(unwrapping: $editableString).map { $string in - VStack { - TextField("Edit string", text: $string) - HStack { - Button("Discard") { - editableString = nil - } - Spacer() - Button("Save") { - string = string - editableString = nil - } - } - } - } - if editableString == nil { - Text("\(string)") - Button("Edit") { - editableString = string - } - } - } - .buttonStyle(.borderless) - } -} - -#Preview { - IfLetCaseStudy() -} diff --git a/Examples/CaseStudies/12-IfCaseLet.swift b/Examples/CaseStudies/12-IfCaseLet.swift deleted file mode 100644 index 5994f5898..000000000 --- a/Examples/CaseStudies/12-IfCaseLet.swift +++ /dev/null @@ -1,55 +0,0 @@ -import CasePaths -import SwiftUI -import SwiftUINavigation - -private let readMe = """ - This demonstrates how to destructure a binding of an enum into a binding of one of its cases. - - Tap the "Edit" button to put the form into edit mode. Then you can make changes to the message \ - and either commit the changes by tapping "Save", or discard the changes by tapping "Discard". - """ - -struct IfCaseLetCaseStudy: View { - @State var string: String = "Hello" - @State var editableString: EditableString = .inactive - - @CasePathable - enum EditableString { - case active(String) - case inactive - } - - var body: some View { - Form { - Section { - Text(readMe) - } - $editableString.active.map { $string in - VStack { - TextField("Edit string", text: $string) - HStack { - Button("Discard", role: .cancel) { - editableString = .inactive - } - Spacer() - Button("Save") { - string = string - editableString = .inactive - } - } - } - } - if !editableString.is(\.active) { - Text("\(string)") - Button("Edit") { - editableString = .active(string) - } - } - } - .buttonStyle(.borderless) - } -} - -#Preview { - IfCaseLetCaseStudy() -} diff --git a/Examples/CaseStudies/Internal/CaseStudy.swift b/Examples/CaseStudies/Internal/CaseStudy.swift new file mode 100644 index 000000000..9012fb85e --- /dev/null +++ b/Examples/CaseStudies/Internal/CaseStudy.swift @@ -0,0 +1,222 @@ +import SwiftUI +import UIKitNavigation + +protocol CaseStudy { + var readMe: String { get } + var caseStudyTitle: String { get } + var caseStudyNavigationTitle: String { get } + var usesOwnLayout: Bool { get } + var isPresentedInSheet: Bool { get } +} +protocol SwiftUICaseStudy: CaseStudy, View {} +protocol UIKitCaseStudy: CaseStudy, UIViewController {} + +extension CaseStudy { + var caseStudyNavigationTitle: String { caseStudyTitle } + var isPresentedInSheet: Bool { false } +} +extension SwiftUICaseStudy { + var usesOwnLayout: Bool { false } +} +extension UIKitCaseStudy { + var usesOwnLayout: Bool { true } +} + +@resultBuilder +@MainActor +enum CaseStudyViewBuilder { + @ViewBuilder + static func buildBlock() -> some View {} + @ViewBuilder + static func buildExpression(_ caseStudy: some SwiftUICaseStudy) -> some View { + SwiftUICaseStudyButton(caseStudy: caseStudy) + } + @ViewBuilder + static func buildExpression(_ caseStudy: some UIKitCaseStudy) -> some View { + UIKitCaseStudyButton(caseStudy: caseStudy) + } + static func buildPartialBlock(first: some View) -> some View { + first + } + @ViewBuilder + static func buildPartialBlock(accumulated: some View, next: some View) -> some View { + accumulated + next + } +} + +struct SwiftUICaseStudyButton: View { + let caseStudy: C + @State var isPresented = false + var body: some View { + if caseStudy.isPresentedInSheet { + Button(caseStudy.caseStudyTitle) { + isPresented = true + } + .sheet(isPresented: $isPresented) { + CaseStudyView { + caseStudy + } + .modifier(CaseStudyModifier(caseStudy: caseStudy)) + } + } else { + NavigationLink(caseStudy.caseStudyTitle) { + CaseStudyView { + caseStudy + } + .modifier(CaseStudyModifier(caseStudy: caseStudy)) + } + } + } +} + +struct UIKitCaseStudyButton: View { + let caseStudy: C + @State var isPresented = false + var body: some View { + if caseStudy.isPresentedInSheet { + Button(caseStudy.caseStudyTitle) { + isPresented = true + } + .sheet(isPresented: $isPresented) { + UIViewControllerRepresenting { + ( + (caseStudy as? UINavigationController) + ?? UINavigationController(rootViewController: caseStudy) + ) + .setUp(caseStudy: caseStudy) + } + .modifier(CaseStudyModifier(caseStudy: caseStudy)) + } + } else { + NavigationLink(caseStudy.caseStudyTitle) { + UIViewControllerRepresenting { + caseStudy + } + .modifier(CaseStudyModifier(caseStudy: caseStudy)) + } + } + } +} + +extension UINavigationController { + func setUp(caseStudy: some CaseStudy) -> Self { + self.viewControllers[0].title = caseStudy.caseStudyNavigationTitle + self.viewControllers[0].navigationItem.rightBarButtonItem = UIBarButtonItem( + title: "About", + primaryAction: UIAction { [weak self] _ in + self?.present( + UIHostingController( + rootView: Form { + Text(template: caseStudy.readMe) + } + .presentationDetents([.medium]) + ), + animated: true + ) + }) + return self + } +} + +struct CaseStudyModifier: ViewModifier { + let caseStudy: C + @State var isAboutPresented = false + func body(content: Content) -> some View { + content + .navigationTitle(caseStudy.caseStudyNavigationTitle) + .toolbar { + ToolbarItem { + Button("About") { isAboutPresented = true } + } + } + .sheet(isPresented: $isAboutPresented) { + Form { + Text(template: caseStudy.readMe) + } + .presentationDetents([.medium]) + } + } +} + +struct CaseStudyView: View { + @ViewBuilder let caseStudy: C + @State var isAboutPresented = false + var body: some View { + if caseStudy.usesOwnLayout { + VStack { + caseStudy + } + } else { + Form { + caseStudy + } + } + } +} + +struct CaseStudyGroupView: View { + @CaseStudyViewBuilder let content: Content + @ViewBuilder let title: Title + + var body: some View { + Section { + content + } header: { + title + } + } +} + +extension CaseStudyGroupView where Title == Text { + init(_ title: String, @CaseStudyViewBuilder content: () -> Content) { + self.init(content: content) { Text(title) } + } +} + +extension SwiftUICaseStudy { + fileprivate func navigationLink() -> some View { + NavigationLink(caseStudyTitle) { + self + } + } +} + +#Preview("SwiftUI case study") { + NavigationStack { + CaseStudyView { + DemoCaseStudy() + } + } +} + +#Preview("SwiftUI case study group") { + NavigationStack { + Form { + CaseStudyGroupView("Group") { + DemoCaseStudy() + } + } + } +} + +private struct DemoCaseStudy: SwiftUICaseStudy { + let caseStudyTitle = "Demo Case Study" + let readMe = """ + Hello! This is a demo case study. + + Enjoy! + """ + var body: some View { + Text("Hello!") + } +} + +private class DemoCaseStudyController: UIViewController, UIKitCaseStudy { + let caseStudyTitle = "Demo Case Study" + let readMe = """ + Hello! This is a demo case study. + + Enjoy! + """ +} diff --git a/Examples/CaseStudies/Internal/DetentsHelper.swift b/Examples/CaseStudies/Internal/DetentsHelper.swift new file mode 100644 index 000000000..6f9ed4144 --- /dev/null +++ b/Examples/CaseStudies/Internal/DetentsHelper.swift @@ -0,0 +1,12 @@ +import UIKit + +extension UIViewController { + func mediumDetents() { + if let sheet = sheetPresentationController { + sheet.detents = [.medium()] + sheet.prefersScrollingExpandsWhenScrolledToEdge = false + sheet.prefersEdgeAttachedInCompactHeight = true + sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true + } + } +} diff --git a/Examples/CaseStudies/FactClient.swift b/Examples/CaseStudies/Internal/FactClient.swift similarity index 90% rename from Examples/CaseStudies/FactClient.swift rename to Examples/CaseStudies/Internal/FactClient.swift index 279b53ff0..7538b6d43 100644 --- a/Examples/CaseStudies/FactClient.swift +++ b/Examples/CaseStudies/Internal/FactClient.swift @@ -4,8 +4,8 @@ struct Fact: Identifiable { var description: String let number: Int - var id: AnyHashable { - [description as AnyHashable, number] + var id: Int { + number } } diff --git a/Examples/CaseStudies/Internal/Text+Template.swift b/Examples/CaseStudies/Internal/Text+Template.swift new file mode 100644 index 000000000..435e8744d --- /dev/null +++ b/Examples/CaseStudies/Internal/Text+Template.swift @@ -0,0 +1,59 @@ +import SwiftUI + +extension Text { + init(template: String, _ style: Font.TextStyle = .body) { + enum Style: Hashable { + case code + case emphasis + case strong + } + + var segments: [Text] = [] + var currentValue = "" + var currentStyles: Set