Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ImageReference, ManagedViewUpdate, CaseIterablePicker and Tiles #45

Merged
merged 29 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b5d5c8e
Add ImageReference from SpeziDevices
Supereg Sep 11, 2024
32e03ee
Make it sendable
Supereg Sep 11, 2024
de70433
Add Tiles and ManagedViewUpdate
Supereg Sep 12, 2024
87ec7a3
Docs and snapshot testing
Supereg Sep 12, 2024
4f48f32
Add a manual refresh option
Supereg Sep 12, 2024
1ef116e
Docs
Supereg Sep 12, 2024
3eb1c90
Add missing snapshot
Supereg Sep 12, 2024
1ee0984
Just combine
Supereg Sep 12, 2024
8810ccf
Image assertions do not work on watchOS
Supereg Sep 12, 2024
dd28a46
Make the completed tile header part of the API
Supereg Sep 12, 2024
797e37e
Add additional tests
Supereg Sep 12, 2024
f3d024f
Restore old beta signature
Supereg Sep 13, 2024
a504413
Support retrieving ImageReference as an UIImage
Supereg Sep 25, 2024
88e7fc4
Fix concurrency errors
Supereg Sep 25, 2024
aa0ac07
Allow to pass in a symbol configuration when creating a UIImage
Supereg Sep 26, 2024
a068802
Revert and helpfer bool
Supereg Sep 26, 2024
286fae2
Fix macOS
Supereg Sep 30, 2024
029a86b
Full support
Supereg Sep 30, 2024
7eb5c52
Remove some CI entries, adjust for screen size and add @Previewable
Supereg Sep 30, 2024
01b7ab4
Make ListRow reuse LabeledContent view and add convenience inits for …
Supereg Oct 17, 2024
b35fcd7
Preview adjustments
Supereg Oct 17, 2024
0c7dc3e
Adding CaseIterablePicker
Supereg Oct 17, 2024
d163509
Update snapshot
Supereg Oct 17, 2024
ab8ca31
Fix localization, fix ambigous overload
Supereg Oct 23, 2024
f49f9ab
Some visuals
Supereg Oct 28, 2024
3e221bd
Embed into docs
Supereg Oct 28, 2024
3afc7ee
Add UI tests for CaseIterablePicker
Supereg Oct 28, 2024
7487242
Adjust some unit tests
Supereg Oct 29, 2024
c005f06
Tests modification
Supereg Oct 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 1 addition & 22 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,6 @@ jobs:
scheme: SpeziViews-Package
resultBundle: SpeziViews-iOS.xcresult
artifactname: SpeziViews-iOS.xcresult
buildandtest_ios_latest:
name: Build and Test Swift Package iOS Latest
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
runsonlabels: '["macOS", "self-hosted"]'
scheme: SpeziViews-Package
xcodeversion: latest
swiftVersion: 6
resultBundle: SpeziViews-iOS-Latest.xcresult
artifactname: SpeziViews-iOS-Latest.xcresult
buildandtest_watchos:
name: Build and Test Swift Package watchOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
Expand Down Expand Up @@ -79,25 +69,14 @@ jobs:
scheme: TestApp
resultBundle: TestApp-iOS.xcresult
artifactname: TestApp-iOS.xcresult
buildandtestuitests_ios_latest:
name: Build and Test UI Tests iOS Latest
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
runsonlabels: '["macOS", "self-hosted"]'
path: Tests/UITests
scheme: TestApp
xcodeversion: latest
swiftVersion: 6
resultBundle: TestApp-iOS-Latest.xcresult
artifactname: TestApp-iOS-Latest.xcresult
buildandtestuitests_ipad:
name: Build and Test UI Tests iPadOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
runsonlabels: '["macOS", "self-hosted"]'
path: Tests/UITests
scheme: TestApp
destination: 'platform=iOS Simulator,name=iPad Pro 11-inch (M4)'
destination: 'platform=iOS Simulator,name=iPad Pro 13-inch (M4)'
resultBundle: TestApp-iPad.xcresult
artifactname: TestApp-iPad.xcresult
buildandtestuitests_visionos:
Expand Down
21 changes: 1 addition & 20 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.9
// swift-tools-version:6.0

//
// This source file is part of the Stanford Spezi open-source project
Expand All @@ -12,13 +12,6 @@ import class Foundation.ProcessInfo
import PackageDescription


#if swift(<6)
let swiftConcurrency: SwiftSetting = .enableExperimentalFeature("StrictConcurrency")
#else
let swiftConcurrency: SwiftSetting = .enableUpcomingFeature("StrictConcurrency")
#endif


let package = Package(
name: "SpeziViews",
defaultLocalization: "en",
Expand All @@ -45,19 +38,13 @@ let package = Package(
dependencies: [
.product(name: "Spezi", package: "Spezi")
],
swiftSettings: [
swiftConcurrency
],
plugins: [] + swiftLintPlugin()
),
.target(
name: "SpeziPersonalInfo",
dependencies: [
.target(name: "SpeziViews")
],
swiftSettings: [
swiftConcurrency
],
plugins: [] + swiftLintPlugin()
),
.target(
Expand All @@ -66,9 +53,6 @@ let package = Package(
.target(name: "SpeziViews"),
.product(name: "OrderedCollections", package: "swift-collections")
],
swiftSettings: [
swiftConcurrency
],
plugins: [] + swiftLintPlugin()
),
.testTarget(
Expand All @@ -78,9 +62,6 @@ let package = Package(
.target(name: "SpeziValidation"),
.product(name: "SnapshotTesting", package: "swift-snapshot-testing")
],
swiftSettings: [
swiftConcurrency
],
plugins: [] + swiftLintPlugin()
)
]
Expand Down
5 changes: 2 additions & 3 deletions Sources/SpeziPersonalInfo/Fields/NameFieldRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ public struct NameFieldRow<Description: View, Label: View>: View {
/// - Parameters:
/// - name: The name to display and edit.
/// - component: The `KeyPath` to the property of the provided `PersonNameComponents` to display and edit.
/// - prompt: An optional `Text` prompt. Refer to the documentation of `TextField` for more information.
/// - description: The description label displayed before the text field.
/// - label: A view that describes the purpose of the text field.
public init(
Expand All @@ -108,7 +107,7 @@ public struct NameFieldRow<Description: View, Label: View>: View {

#if DEBUG
#Preview {
@State var name = PersonNameComponents()
@Previewable @State var name = PersonNameComponents()
return Grid(horizontalSpacing: 15) {
NameFieldRow(name: $name, for: \.familyName) {
Text(verbatim: "First")
Expand All @@ -127,7 +126,7 @@ public struct NameFieldRow<Description: View, Label: View>: View {
}
}
#Preview {
@State var name = PersonNameComponents()
@Previewable @State var name = PersonNameComponents()
return Form {
Grid(horizontalSpacing: 15) {
NameFieldRow(name: $name, for: \.givenName) {
Expand Down
2 changes: 1 addition & 1 deletion Sources/SpeziPersonalInfo/Fields/NameTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public struct NameTextField<Label: View>: View {

#if DEBUG
#Preview {
@State var name = PersonNameComponents()
@Previewable @State var name = PersonNameComponents()
return List {
NameTextField(name: $name, for: \.givenName) {
Text(verbatim: "enter first name")
Expand Down
2 changes: 1 addition & 1 deletion Sources/SpeziValidation/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Diese Feld kann nicht leer sein."
"value" : "Dieses Feld kann nicht leer sein."
}
},
"en" : {
Expand Down
16 changes: 8 additions & 8 deletions Sources/SpeziValidation/SpeziValidation.docc/SpeziValidation.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ The library is based on a rule-based approach using ``ValidationRule``s.

### Performing Validation

The only thing you have to do, is to set up the ``SwiftUI/View/validate(input:rules:)-5dac4`` modifier for your
The only thing you have to do, is to set up the ``SwiftUICore/View/validate(input:rules:)-5dac4`` modifier for your
text input.
Supply your input and validation rules.

Expand All @@ -50,7 +50,7 @@ property wrapper.
### Managing Validation

Parent views can access the validation state of their child views using the ``ValidationState`` property wrapper
and the ``SwiftUI/View/receiveValidation(in:)`` modifier.
and the ``SwiftUICore/View/receiveValidation(in:)`` modifier.

The code example below shows
how you can use the validation state of your subview to perform final validation on a button press.
Expand Down Expand Up @@ -79,19 +79,19 @@ var body: some View {
### Performing Validation

- ``ValidationRule``
- ``SwiftUI/View/validate(input:rules:)-5dac4``
- ``SwiftUI/View/validate(input:rules:)-9vks0``
- ``SwiftUI/View/validate(_:message:)``
- ``SwiftUICore/View/validate(input:rules:)-5dac4``
- ``SwiftUICore/View/validate(input:rules:)-9vks0``
- ``SwiftUICore/View/validate(_:message:)``

### Managing Validation

- ``ValidationState``
- ``SwiftUI/View/receiveValidation(in:)``
- ``SwiftUICore/View/receiveValidation(in:)``

### Configuration

- ``SwiftUI/EnvironmentValues/validationConfiguration``
- ``SwiftUI/EnvironmentValues/validationDebounce``
- ``SwiftUICore/EnvironmentValues/validationConfiguration``
- ``SwiftUICore/EnvironmentValues/validationDebounce``

### Visualizing Validation

Expand Down
4 changes: 2 additions & 2 deletions Sources/SpeziValidation/ValidationEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@ public class ValidationEngine: Identifiable {

/// Access the configuration of the validation engine.
///
/// You may use the ``SwiftUI/EnvironmentValues/validationConfiguration`` environment key to configure this value from
/// You may use the ``SwiftUICore/EnvironmentValues/validationConfiguration`` environment key to configure this value from
/// the environment.
public var configuration: Configuration
/// The configurable debounce duration for input submission.
///
/// This duration is used to debounce repeated calls to ``submit(input:debounce:)`` where `debounce` is set to `true`.
/// You may use the ``SwiftUI/EnvironmentValues/validationDebounce`` environment key to configure this value from
/// You may use the ``SwiftUICore/EnvironmentValues/validationDebounce`` environment key to configure this value from
/// the environment.
public var debounceDuration: Duration

Expand Down
2 changes: 1 addition & 1 deletion Sources/SpeziValidation/ValidationRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ enum CascadingValidationEffect {
/// )
/// ```
///
/// Use the ``SwiftUI/View/validate(input:rules:)-5dac4`` modifier to apply a validation rule to a given `String` input.
/// Use the ``SwiftUICore/View/validate(input:rules:)-5dac4`` modifier to apply a validation rule to a given `String` input.
///
/// ### Discussion on security-related client-side Validation
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import SwiftUI
/// To do so, you would typically call ``ValidationContext/validateSubviews(switchFocus:)`` within the `Button`
/// action. This call can be used to automatically switch focus to the first field that failed validation.
///
/// The `ValidationState` property wrapper works in conjunction with the ``SwiftUI/View/receiveValidation(in:)`` modifier
/// The `ValidationState` property wrapper works in conjunction with the ``SwiftUICore/View/receiveValidation(in:)`` modifier
/// to receive validation state from the child views.
///
/// Below is a short code example of a typical setup:
Expand Down
2 changes: 1 addition & 1 deletion Sources/SpeziValidation/Views/VerifiableTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public struct VerifiableTextField<FieldLabel: View, FieldFooter: View>: View {

#if DEBUG
#Preview {
@State var text = ""
@Previewable @State var text = ""
return Form {
VerifiableTextField(text: $text) {
Text(verbatim: "Password Text")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import SwiftUI
/// This might be helpful for views that rely on ``AnyLocalizedError``. Outer views can define a
/// sensible default for a localized default error description in the case that a sub-view has to display
/// an ``AnyLocalizedError`` for a generic error.
struct DefaultErrorDescription: EnvironmentKey {
private struct DefaultErrorDescription: EnvironmentKey {
static let defaultValue: LocalizedStringResource? = nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import SwiftUI
///
/// This might be helpful to provide extensive customization points without introducing clutter in the initializer of views.
/// The ``AsyncButton`` is one example where this `EnvironmentKey` is used.
struct ProcessingDebounceDuration: EnvironmentKey {
private struct ProcessingDebounceDuration: EnvironmentKey {
static let defaultValue: Duration = .milliseconds(150)
}

Expand Down
91 changes: 91 additions & 0 deletions Sources/SpeziViews/Model/ImageReference.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//
// This source file is part of the Stanford Spezi open-project
//
// SPDX-FileCopyrightText: 2024 Stanford University
//
// SPDX-License-Identifier: MIT
//

import SwiftUI


/// Reference an Image Resource.
public enum ImageReference {
Supereg marked this conversation as resolved.
Show resolved Hide resolved
/// Provides the system name for an image.
case system(String)
/// Reference an image from the asset catalog of a bundle.
case asset(String, bundle: Bundle? = nil)


/// A system image is referenced.
public var isSystemImage: Bool {
if case .system = self {
true
} else {
false
}
}
}


extension ImageReference {
/// Retrieve Image.
///
/// Returns `nil` if the image resource could not be located.
public var image: Image? {
switch self {
case let .system(name):
return Image(systemName: name)
case let .asset(name, bundle: bundle):
#if canImport(UIKit)
// also available on watchOS
guard UIImage(named: name, in: bundle, with: nil) != nil else {
return nil
}
#elseif canImport(AppKit)
guard NSImage(named: name) != nil else {
return nil
}
#endif
return Image(name, bundle: bundle)

Check warning on line 50 in Sources/SpeziViews/Model/ImageReference.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziViews/Model/ImageReference.swift#L50

Added line #L50 was not covered by tests
}
}

#if canImport(UIKit) // also available on watchOS
/// Retrieve an UIImage.
///
/// Returns `nil` if the image resource could not be located.
public var uiImage: UIImage? {
switch self {
case let .system(name):
UIImage(systemName: name)
case let .asset(name, bundle):
UIImage(named: name, in: bundle, with: nil)
}
}

#if canImport(WatchKit)
/// Retrieve a WKImage.
///
/// Returns `nil` if the image resource could not be located.
public var wkImage: WKImage? {
uiImage.map { WKImage(image: $0) }
}
#endif
#elseif canImport(AppKit)
/// Retrieve a NSImage.
///
/// Returns `nil` if the image resource could not be located.
public var nsImage: NSImage? {
switch self {
case let .system(name):
NSImage(systemSymbolName: name, accessibilityDescription: nil)
case let .asset(name, _):
NSImage(named: name)
}
}
#endif
}


extension ImageReference: Hashable, Sendable {}
4 changes: 2 additions & 2 deletions Sources/SpeziViews/Model/OperationState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
///
/// The ``OperationState`` encapsulates the core state of an application's behavior, which directly impacts the user interface and interaction.
/// To effectively manage the UI's state in the Spezi framework, the ``OperationState`` can be represented as a ``ViewState``.
/// This bridging mechanism allows Spezi to monitor and respond to changes in the view's state, for example via the ``SwiftUI/View/viewStateAlert(state:)-27a86`` view modifier.
/// This bridging mechanism allows Spezi to monitor and respond to changes in the view's state, for example via the ``SwiftUICore/View/viewStateAlert(state:)-27a86`` view modifier.
///
/// - Note: It's important to note that this conversion is a lossy process, where a potentially intricate ``OperationState`` is
/// distilled into a simpler ``ViewState``.
Expand Down Expand Up @@ -66,7 +66,7 @@
///
/// > Tip:
/// > In the case that no SwiftUI `Binding` to the ``ViewState`` of the ``OperationState`` (so ``OperationState/representation``)
/// > is required (e.g., no use of the ``SwiftUI/View/viewStateAlert(state:)-4wzs4`` view modifier), one is able to omit the separately defined ``ViewState``
/// > is required (e.g., no use of the ``SwiftUICore/View/viewStateAlert(state:)-4wzs4`` view modifier), one is able to omit the separately defined ``ViewState``
/// > within a SwiftUI `View` and directly access the ``OperationState/representation`` property.
public protocol OperationState {
/// Defines the lossy abstraction logic from the possibly complex ``OperationState`` to the simple ``ViewState``.
Expand Down
2 changes: 1 addition & 1 deletion Sources/SpeziViews/Model/ViewState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import Foundation
/// A `ViewState` provides a built-in mechanism for tracking the state of a Spezi UI component.
/// A view can be in one of three states: `idle`, `processing`, or `error`.
///
/// The ``SwiftUI/View/viewStateAlert(state:)-4wzs4`` view modifier can be used to automatically notify users with an
/// The ``SwiftUICore/View/viewStateAlert(state:)-4wzs4`` view modifier can be used to automatically notify users with an
/// [`Alert`](https://developer.apple.com/documentation/swiftui/view/alert(_:ispresented:actions:)-3npin) when the
/// `ViewState` transitions into an error state.
///
Expand Down
Loading
Loading