├── .editorconfig ├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml └── workflows │ ├── ci.yml │ ├── format.yml │ └── release.yml ├── .gitignore ├── .spi.yml ├── Examples ├── CaseStudies │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── CaseStudiesApp.swift │ ├── Info.plist │ ├── Internal │ │ ├── CaseStudy.swift │ │ ├── DetentsHelper.swift │ │ ├── FactClient.swift │ │ └── Text+Template.swift │ ├── RootView.swift │ ├── SwiftUI │ │ ├── AlertDialogState.swift │ │ ├── EnumControls.swift │ │ ├── EnumNavigation.swift │ │ ├── OptionalNavigation.swift │ │ ├── SwiftUICaseStudies.swift │ │ └── SynchronizedBindings.swift │ └── UIKit │ │ ├── AnimationsViewController.swift │ │ ├── BasicsNavigationViewController.swift │ │ ├── ConciseEnumNavigationViewController.swift │ │ ├── EnumControlsViewController.swift │ │ ├── ErasedNavigationStackController.swift │ │ ├── FocusViewController.swift │ │ ├── MinimalObservationViewController.swift │ │ ├── StaticNavigationStackController.swift │ │ ├── UIControlBindingsViewController.swift │ │ ├── UIKitCaseStudies.swift │ │ └── WiFiFeature │ │ ├── ConnectToNetworkFeature.swift │ │ ├── Network.swift │ │ ├── NetworkDetailFeature.swift │ │ └── WiFiSettingsFeature.swift ├── CaseStudiesTests │ ├── CaseStudies.xctestplan │ ├── Internal │ │ ├── AssertEventually.swift │ │ ├── SetUp.swift │ │ └── XCTTODO.swift │ ├── NavigationPathTests.swift │ ├── NavigationStackTests.swift │ ├── PresentationTests.swift │ └── RuntimeWarningTests.swift ├── DynamicFramework │ ├── DynamicFramework.h │ └── Import.swift ├── Examples.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcshareddata │ │ └── xcschemes │ │ ├── CaseStudies.xcscheme │ │ └── Inventory.xcscheme ├── Inventory │ ├── App.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Inventory.swift │ ├── Item.swift │ └── ItemRow.swift └── Package.swift ├── LICENSE ├── Makefile ├── Package.resolved ├── Package.swift ├── Package@swift-6.0.swift ├── README.md ├── Sources ├── AppKitNavigation │ ├── AppKitAnimation.swift │ ├── Documentation.docc │ │ ├── AppKitNavigation.md │ │ └── Extensions │ │ │ └── AppKitAnimation.md │ ├── Internal │ │ └── Exports.swift │ ├── SwiftUI │ │ └── Representable.swift │ ├── UIBinding.swift │ └── UITransaction.swift ├── SwiftNavigation │ ├── AlertState.swift │ ├── Bind.swift │ ├── Binding.swift │ ├── ButtonState.swift │ ├── ButtonStateBuilder.swift │ ├── ConfirmationDialogState.swift │ ├── Documentation.docc │ │ ├── Articles │ │ │ ├── CrossPlatform.md │ │ │ └── WhatIsNavigation.md │ │ ├── Extensions │ │ │ ├── AlertState.md │ │ │ ├── ButtonState.md │ │ │ ├── ConfirmationDialogState.md │ │ │ ├── NSObjectObserve.md │ │ │ ├── Observe.md │ │ │ ├── TextState.md │ │ │ ├── UIBindable.md │ │ │ └── UIBinding.md │ │ └── SwiftNavigation.md │ ├── HashableObject.swift │ ├── Internal │ │ ├── AssumeIsolated.swift │ │ ├── Deprecations.swift │ │ ├── ErrorMechanism.swift │ │ ├── Exports.swift │ │ ├── HashableStaticString.swift │ │ ├── KeyPath+Sendable.swift │ │ └── ToOptionalUnit.swift │ ├── NSObject+Observe.swift │ ├── Observe.swift │ ├── TextState.swift │ ├── UIBindable.swift │ ├── UIBinding.swift │ ├── UINavigationPath.swift │ └── UITransaction.swift ├── SwiftUINavigation │ ├── Alert.swift │ ├── Binding.swift │ ├── ConfirmationDialog.swift │ ├── Documentation.docc │ │ ├── Articles │ │ │ ├── AlertsDialogs.md │ │ │ ├── Bindings.md │ │ │ ├── Navigation.md │ │ │ ├── SheetsPopoversCovers.md │ │ │ └── SwiftUINavigationTools.md │ │ └── SwiftUINavigation.md │ ├── FullScreenCover.swift │ ├── Internal │ │ ├── Binding+Internal.swift │ │ ├── Exports.swift │ │ └── Identified.swift │ ├── NavigationDestination.swift │ ├── NavigationLink.swift │ ├── Popover.swift │ ├── Sheet.swift │ └── WithState.swift ├── UIKitNavigation │ ├── Bindings │ │ ├── UIColorWell.swift │ │ ├── UIControl.swift │ │ ├── UIDatePicker.swift │ │ ├── UIPageControl.swift │ │ ├── UISegmentedControl.swift │ │ ├── UISlider.swift │ │ ├── UIStepper.swift │ │ ├── UISwitch.swift │ │ ├── UITabBarController.swift │ │ └── UITextField.swift │ ├── Documentation.docc │ │ ├── Extensions │ │ │ ├── UIColorWell.md │ │ │ ├── UIControlProtocol.md │ │ │ ├── UIDatePicker.md │ │ │ ├── UIKitAnimation.md │ │ │ ├── UIPageControl.md │ │ │ ├── UISlider.md │ │ │ ├── UIStepper.md │ │ │ ├── UISwitch.md │ │ │ ├── UITextField.md │ │ │ └── UIViewController.md │ │ └── UIKitNavigation.md │ ├── Internal │ │ ├── Exports.swift │ │ └── PopFromViewController.swift │ ├── Navigation │ │ ├── Dismiss.swift │ │ ├── NavigationStackController.swift │ │ ├── Presentation.swift │ │ ├── Push.swift │ │ └── UIAlertController.swift │ ├── SwiftUI │ │ └── Representable.swift │ ├── UIBinding.swift │ ├── UIKitAnimation.swift │ └── UITransaction.swift └── UIKitNavigationShim │ ├── include │ └── shim.h │ └── shim.m ├── SwiftNavigation.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ ├── swiftpm │ └── Package.resolved │ └── xcschemes │ ├── SwiftNavigation.xcscheme │ ├── SwiftUINavigation.xcscheme │ └── UIKitNavigation.xcscheme └── Tests ├── SwiftNavigation.xctestplan ├── SwiftNavigationTests ├── ButtonStateTests.swift ├── IsolationTests.swift ├── LifetimeTests.swift ├── ObserveTests.swift ├── TextStateTests.swift ├── UIBindableTests.swift ├── UIBindingTests.swift ├── UINavigationPathTests.swift └── UITransactionTests.swift ├── SwiftUINavigation.xctestplan ├── SwiftUINavigationTests ├── AlertTests.swift ├── BindingTests.swift └── SwiftUINavigationTests.swift ├── UIKitNavigation.xctestplan └── UIKitNavigationTests ├── MemoryManagementTests.swift ├── ObserveTests.swift ├── UIControlTests.swift ├── UITransactionTests.swift └── ViewControllerRepresentingTests.swift /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Something isn't working as expected 3 | labels: [bug] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you for contributing to the SwiftUI Navigation! 9 | 10 | Before you submit your issue, please complete each text area below with the relevant details for your bug, and complete the steps in the checklist. 11 | - type: textarea 12 | attributes: 13 | label: Description 14 | description: | 15 | A short description of the incorrect behavior. 16 | 17 | If you think this issue has been recently introduced and did not occur in an earlier version, please note that. If possible, include the last version that the behavior was correct in addition to your current version. 18 | validations: 19 | required: true 20 | - type: checkboxes 21 | attributes: 22 | label: Checklist 23 | options: 24 | - label: I have determined whether this bug is also reproducible in a vanilla SwiftUI project. 25 | required: false 26 | - label: If possible, I've reproduced the issue using the `main` branch of this package. 27 | required: false 28 | - label: This issue hasn't been addressed in an [existing GitHub issue](https://github.com/pointfreeco/swift-navigation/issues) or [discussion](https://github.com/pointfreeco/swiftui-navigation/discussions). 29 | required: true 30 | - type: textarea 31 | attributes: 32 | label: Expected behavior 33 | description: Describe what you expected to happen. 34 | validations: 35 | required: false 36 | - type: textarea 37 | attributes: 38 | label: Actual behavior 39 | description: Describe or copy/paste the behavior you observe. 40 | validations: 41 | required: false 42 | - type: textarea 43 | attributes: 44 | label: Steps to reproduce 45 | description: | 46 | Explanation of how to reproduce the incorrect behavior. 47 | 48 | This could include an attached project or link to code that is exhibiting the issue, and/or a screen recording. 49 | placeholder: | 50 | 1. ... 51 | validations: 52 | required: false 53 | - type: input 54 | attributes: 55 | label: SwiftUI Navigation version information 56 | description: The version of SwiftUI Navigation used to reproduce this issue. 57 | placeholder: "'0.7.0' for example, or a commit hash" 58 | - type: input 59 | attributes: 60 | label: Destination operating system 61 | description: The OS running your application. 62 | placeholder: "'iOS 16' for example" 63 | - type: input 64 | attributes: 65 | label: Xcode version information 66 | description: The version of Xcode used to reproduce this issue. 67 | placeholder: "The version displayed from 'Xcode 〉About Xcode'" 68 | - type: textarea 69 | attributes: 70 | label: Swift Compiler version information 71 | description: The version of Swift used to reproduce this issue. 72 | placeholder: Output from 'xcrun swiftc --version' 73 | render: shell 74 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | contact_links: 4 | - name: Project Discussion 5 | url: https://github.com/pointfreeco/swift-navigation/discussions 6 | about: SwiftUI Navigation Q&A, ideas, and more 7 | - name: Documentation 8 | url: https://pointfreeco.github.io/swift-navigation/main/documentation/swiftnavigation/ 9 | about: Read SwiftUI Navigation's documentation 10 | - name: Videos 11 | url: https://www.pointfree.co/ 12 | about: Watch videos to get a behind-the-scenes look at how SwiftUI Navigation was motivated and built 13 | - name: Slack 14 | url: https://www.pointfree.co/slack-invite 15 | about: Community chat 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - '*' 10 | workflow_dispatch: 11 | 12 | concurrency: 13 | group: ci-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | library: 18 | runs-on: macos-15 19 | strategy: 20 | matrix: 21 | xcode: 22 | - '16.2' 23 | variation: 24 | - ios 25 | - macos 26 | - tvos 27 | - watchos 28 | - examples 29 | 30 | steps: 31 | - uses: actions/checkout@v4 32 | - name: Select Xcode ${{ matrix.xcode }} 33 | run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app 34 | - name: Skip macro validation 35 | run: defaults write com.apple.dt.Xcode IDESkipMacroFingerprintValidation -bool YES 36 | - name: Run tests 37 | run: make test-${{ matrix.variation }} 38 | 39 | library-15-4-compatibility: 40 | runs-on: macos-14 41 | strategy: 42 | matrix: 43 | xcode: 44 | - '15.4' 45 | ios_version: 46 | - '17.5' 47 | variation: 48 | - ios 49 | steps: 50 | - uses: actions/checkout@v4 51 | - name: Select Xcode ${{ matrix.xcode }} 52 | run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app 53 | - name: Skip macro validation 54 | run: defaults write com.apple.dt.Xcode IDESkipMacroFingerprintValidation -bool YES 55 | - name: Run tests 56 | run: make IOS_VERSION=${{matrix.ios_version}} test-${{ matrix.variation }} 57 | 58 | library-evolution: 59 | name: Library Evolution 60 | runs-on: macos-15 61 | strategy: 62 | matrix: 63 | xcode: 64 | - '16.2' 65 | steps: 66 | - uses: actions/checkout@v4 67 | - name: Select Xcode ${{ matrix.xcode }} 68 | run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app 69 | - name: Skip macro validation 70 | run: defaults write com.apple.dt.Xcode IDESkipMacroFingerprintValidation -bool YES 71 | - name: Build for Library Evolution 72 | run: make build-for-library-evolution 73 | 74 | wasm: 75 | name: Wasm 76 | runs-on: ubuntu-latest 77 | steps: 78 | - uses: actions/checkout@v4 79 | - uses: bytecodealliance/actions/wasmtime/setup@v1 80 | - name: Install Swift and Swift SDK for WebAssembly 81 | run: | 82 | PREFIX=/opt/swift 83 | set -ex 84 | curl -f -o /tmp/swift.tar.gz "https://download.swift.org/swift-6.0.3-release/ubuntu2204/swift-6.0.3-RELEASE/swift-6.0.3-RELEASE-ubuntu22.04.tar.gz" 85 | sudo mkdir -p $PREFIX; sudo tar -xzf /tmp/swift.tar.gz -C $PREFIX --strip-component 1 86 | $PREFIX/usr/bin/swift sdk install https://github.com/swiftwasm/swift/releases/download/swift-wasm-6.0.3-RELEASE/swift-wasm-6.0.3-RELEASE-wasm32-unknown-wasi.artifactbundle.zip --checksum 31d3585b06dd92de390bacc18527801480163188cd7473f492956b5e213a8618 87 | echo "$PREFIX/usr/bin" >> $GITHUB_PATH 88 | 89 | - name: Build tests 90 | run: swift build --swift-sdk wasm32-unknown-wasi --build-tests -Xlinker -z -Xlinker stack-size=$((1024 * 1024)) 91 | - name: Run tests 92 | run: wasmtime --dir . .build/debug/swift-navigationPackageTests.wasm 93 | 94 | # windows: 95 | # name: Windows 96 | # strategy: 97 | # matrix: 98 | # os: [windows-latest] 99 | # config: ['debug', 'release'] 100 | # fail-fast: false 101 | # runs-on: ${{ matrix.os }} 102 | # steps: 103 | # - uses: compnerd/gha-setup-swift@main 104 | # with: 105 | # branch: swift-5.10-release 106 | # tag: 5.10-RELEASE 107 | # - uses: actions/checkout@v4 108 | # - name: Build 109 | # run: swift build -c ${{ matrix.config }} 110 | -------------------------------------------------------------------------------- /.github/workflows/format.yml: -------------------------------------------------------------------------------- 1 | name: Format 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | swift_format: 10 | name: swift-format 11 | runs-on: macOS-14 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Xcode Select 15 | run: sudo xcode-select -s /Applications/Xcode_15.4.app 16 | - name: Install 17 | run: brew install swift-format 18 | - name: Format 19 | run: make format 20 | - uses: stefanzweifel/git-auto-commit-action@v4 21 | with: 22 | commit_message: Run swift-format 23 | branch: 'main' 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | release: 4 | types: [published] 5 | workflow_dispatch: 6 | jobs: 7 | project-channel: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Dump Github context 11 | env: 12 | GITHUB_CONTEXT: ${{ toJSON(github) }} 13 | run: echo "$GITHUB_CONTEXT" 14 | - name: Slack Notification on SUCCESS 15 | if: success() 16 | uses: tokorom/action-slack-incoming-webhook@main 17 | env: 18 | INCOMING_WEBHOOK_URL: ${{ secrets.SLACK_PROJECT_CHANNEL_WEBHOOK_URL }} 19 | with: 20 | text: swift-navigation ${{ github.event.release.tag_name }} has been released. 21 | blocks: | 22 | [ 23 | { 24 | "type": "header", 25 | "text": { 26 | "type": "plain_text", 27 | "text": "swift-navigation ${{ github.event.release.tag_name}}" 28 | } 29 | }, 30 | { 31 | "type": "section", 32 | "text": { 33 | "type": "mrkdwn", 34 | "text": ${{ toJSON(github.event.release.body) }} 35 | } 36 | }, 37 | { 38 | "type": "section", 39 | "text": { 40 | "type": "mrkdwn", 41 | "text": "${{ github.event.release.html_url }}" 42 | } 43 | } 44 | ] 45 | 46 | releases-channel: 47 | runs-on: ubuntu-latest 48 | steps: 49 | - name: Dump Github context 50 | env: 51 | GITHUB_CONTEXT: ${{ toJSON(github) }} 52 | run: echo "$GITHUB_CONTEXT" 53 | - name: Slack Notification on SUCCESS 54 | if: success() 55 | uses: tokorom/action-slack-incoming-webhook@main 56 | env: 57 | INCOMING_WEBHOOK_URL: ${{ secrets.SLACK_RELEASES_WEBHOOK_URL }} 58 | with: 59 | text: swift-navigation ${{ github.event.release.tag_name }} has been released. 60 | blocks: | 61 | [ 62 | { 63 | "type": "header", 64 | "text": { 65 | "type": "plain_text", 66 | "text": "swift-navigation ${{ github.event.release.tag_name}}" 67 | } 68 | }, 69 | { 70 | "type": "section", 71 | "text": { 72 | "type": "mrkdwn", 73 | "text": ${{ toJSON(github.event.release.body) }} 74 | } 75 | }, 76 | { 77 | "type": "section", 78 | "text": { 79 | "type": "mrkdwn", 80 | "text": "${{ github.event.release.html_url }}" 81 | } 82 | } 83 | ] 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /.swiftpm 4 | /Packages 5 | /*.xcodeproj 6 | xcuserdata/ 7 | DerivedData/ 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: 5 | - SwiftNavigation 6 | - AppKitNavigation 7 | - SwiftUINavigation 8 | - UIKitNavigation 9 | swift_version: 6.0 10 | -------------------------------------------------------------------------------- /Examples/CaseStudies/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/CaseStudies/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/CaseStudies/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/CaseStudies/CaseStudiesApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct CaseStudiesApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | RootView() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Examples/CaseStudies/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Examples/CaseStudies/Internal/DetentsHelper.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UIViewController { 4 | func mediumDetents() { 5 | if let sheet = sheetPresentationController { 6 | sheet.detents = [.medium()] 7 | sheet.prefersScrollingExpandsWhenScrolledToEdge = false 8 | sheet.prefersEdgeAttachedInCompactHeight = true 9 | sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/CaseStudies/Internal/FactClient.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Fact: Identifiable { 4 | var description: String 5 | let number: Int 6 | 7 | var id: Int { 8 | number 9 | } 10 | } 11 | 12 | func getNumberFact(_ count: Int) async -> Fact { 13 | let fact: String 14 | do { 15 | let (data, _) = try await URLSession.shared.data( 16 | from: URL(string: "http://numbersapi.com/\(count)/trivia")! 17 | ) 18 | fact = String(decoding: data, as: UTF8.self) 19 | } catch { 20 | // Sometimes numbersapi.com can be flakey, so if it ever fails we will just 21 | // default to a mock response. 22 | fact = "\(count) is a good number Brent" 23 | } 24 | try? await Task.sleep(nanoseconds: NSEC_PER_SEC) 25 | return Fact(description: fact, number: count) 26 | } 27 | -------------------------------------------------------------------------------- /Examples/CaseStudies/Internal/Text+Template.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | extension Text { 4 | init(template: String, _ style: Font.TextStyle = .body) { 5 | enum Style: Hashable { 6 | case code 7 | case emphasis 8 | case strong 9 | } 10 | 11 | var segments: [Text] = [] 12 | var currentValue = "" 13 | var currentStyles: Set