generated from StanfordBDHG/SwiftPackageTemplate
-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce SpeziPersonalInfo target (#20)
# Introduce SpeziPersonalInfo target ## ♻️ Current situation & Problem Previously, `SpeziViews` was treated like a catch all for any UI-component-based API interfaces. This PR introduces `SpeziPersonInfo` that paves the way for a more granular grouping of UI components. In this PR we move out any UI components that deal with personal information. There might be future additions coming from a SpeziAccount refactoring. This brings some breaking changes, where we removed the `NameFields` view and instead replaced it with new optimized views `NameTextField` and `NameFieldRow`. These address specific parts of a `PersonNameComponents` and remove a lot of the complexity like focus state handling. As you now can directly access the individual TextFields, this can be more cleanly handled by the users themselves. Further this PR removes the unused `HTMLView` (and `DocumentView`). `MarkdownView` remains. This PR updates the project to target a deployment target of iOS 17 and migrates to use the new String Catalogs. We made sure to use the explicit `init(verbatim:)` initializer for `Text` views where necessary. We exposed similar functionality with our `LazyText` und `Label` views. External parameters were changed and induce a breaking change. ## ⚙️ Release Notes * The new `SpeziPersonalInfo` target is the entry point for all View components that deal with personal information. * The `NameFields` view was removed and replaced with row-based views `NameTextField` and `NameFieldRow`. * The `HTMLView` and `DocumentView` were removed. * New initializer argument labels for `LazyText` and `Label` views. * Added Documentation Catalogs to structure documentation more clearly. * Upgraded to use String Catalogs. * Updated target platform to iOS 17. ## 📚 Documentation The PR adds documentation catalogs to both targets which were previously missing. Through these documentation catalogs we create a structure that allows to more easily explore SpeziViews and SpeziPersonalInfo packages. Certain documentation was optimized to more clearly communicate the technical details. ## ✅ Testing Testing was update to separate functionality of the two new targets to provide a clear separation between both. ## 📝 Code of Conduct & Contributing Guidelines By submitting creating this pull request, you agree to follow our [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md): - [x] I agree to follow the [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md).
- Loading branch information
Showing
48 changed files
with
1,002 additions
and
903 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
// | ||
// This source file is part of the Stanford Spezi open-source project | ||
// | ||
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
|
||
import SpeziViews | ||
import SwiftUI | ||
|
||
|
||
/// A `NameTextField` that always shows a description in front of the text field. | ||
/// | ||
/// The `NameFieldRow` uses the `DescriptionGridRow` and is to be placed into a [Grid](https://developer.apple.com/documentation/swiftui/grid) | ||
/// view to provide a description text in front of the ``NameTextField``. | ||
/// | ||
/// Below is a short code example on how to collect both the given name and family name of a person within a SwiftUI `Form`. | ||
/// ```swift | ||
/// @State private var name = PersonNameComponents() | ||
/// | ||
/// var body: some View { | ||
/// Form { | ||
/// Grid(horizontalSpacing: 15) { // optional horizontal spacing | ||
/// NameFieldRow(name: $name, for: \.givenName) { | ||
/// Text(verbatim: "First") | ||
/// } label: { | ||
/// Text(verbatim: "enter first name") | ||
/// } | ||
/// | ||
/// Divider() | ||
/// .gridCellUnsizedAxes(.horizontal) | ||
/// | ||
/// NameFieldRow(name: $name, for: \.familyName) { | ||
/// Text(verbatim: "Last") | ||
/// } label: { | ||
/// Text(verbatim: "enter last name") | ||
/// } | ||
/// } | ||
/// } | ||
/// } | ||
/// ``` | ||
public struct NameFieldRow<Description: View, Label: View>: View { | ||
private let description: Description | ||
private let label: Label | ||
private let component: WritableKeyPath<PersonNameComponents, String?> | ||
|
||
@Binding private var name: PersonNameComponents | ||
|
||
|
||
public var body: some View { | ||
DescriptionGridRow { | ||
description | ||
} content: { | ||
NameTextField(name: $name, for: component) { | ||
label | ||
} | ||
} | ||
} | ||
|
||
|
||
/// Creates a name text field with a description label. | ||
/// - Parameters: | ||
/// - description: The localized description label displayed before the text field. | ||
/// - name: The name to display and edit. | ||
/// - component: The `KeyPath` to the property of the provided `PersonNameComponents` to display and edit. | ||
/// - label: A view that describes the purpose of the text field. | ||
public init( | ||
_ description: LocalizedStringResource, | ||
name: Binding<PersonNameComponents>, | ||
for component: WritableKeyPath<PersonNameComponents, String?>, | ||
@ViewBuilder label: () -> Label | ||
) where Description == Text { | ||
self.init(name: name, for: component, description: { Text(description) }, label: label) | ||
} | ||
|
||
/// Creates a name text field with a description label. | ||
/// - 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( | ||
name: Binding<PersonNameComponents>, | ||
for component: WritableKeyPath<PersonNameComponents, String?>, | ||
@ViewBuilder description: () -> Description, | ||
@ViewBuilder label: () -> Label | ||
) { | ||
self._name = name | ||
self.component = component | ||
self.description = description() | ||
self.label = label() | ||
} | ||
} | ||
|
||
|
||
#if DEBUG | ||
#Preview { | ||
@State var name = PersonNameComponents() | ||
return Grid(horizontalSpacing: 15) { | ||
NameFieldRow(name: $name, for: \.familyName) { | ||
Text(verbatim: "First") | ||
} label: { | ||
Text(verbatim: "enter first name") | ||
} | ||
|
||
Divider() | ||
.gridCellUnsizedAxes(.horizontal) | ||
|
||
NameFieldRow(name: $name, for: \.familyName) { | ||
Text(verbatim: "Last") | ||
} label: { | ||
Text(verbatim: "enter last name") | ||
} | ||
} | ||
} | ||
#Preview { | ||
@State var name = PersonNameComponents() | ||
return Form { | ||
Grid(horizontalSpacing: 15) { | ||
NameFieldRow(name: $name, for: \.givenName) { | ||
Text(verbatim: "First") | ||
} label: { | ||
Text(verbatim: "enter first name") | ||
} | ||
|
||
Divider() | ||
.gridCellUnsizedAxes(.horizontal) | ||
|
||
NameFieldRow(name: $name, for: \.familyName) { | ||
Text(verbatim: "Last") | ||
} label: { | ||
Text(verbatim: "enter last name") | ||
} | ||
} | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// | ||
// This source file is part of the Stanford Spezi open-source project | ||
// | ||
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
|
||
import SwiftUI | ||
|
||
|
||
/// A TextField for properties of `PersonNameComponents`. | ||
/// | ||
/// The `NameTextField` view allows to create a SwiftUI [TextField](https://developer.apple.com/documentation/swiftui/textfield) for properties | ||
/// of [PersonNameComponents](https://developer.apple.com/documentation/foundation/personnamecomponents). | ||
/// To do so you supply a Binding to your `PersonNameComponents` value and a `KeyPath` to the property of `PersonNameComponents` you are trying to input. | ||
/// | ||
/// `NameTextField` modifies the underlying `TextField` to optimize for name entry and automatically sets modifiers like | ||
/// [textContentType(_:)](https://developer.apple.com/documentation/swiftui/view/textcontenttype(_:)-ufdv). | ||
/// | ||
/// Below is a short code example on how to create an editable text interface for the given name of a person. | ||
/// ```swift | ||
/// @State private var name = PersonNameComponents() | ||
/// | ||
/// var body: some View { | ||
/// NameTextField("enter first name", name: $name, for: \.givenName) | ||
/// } | ||
/// ``` | ||
/// | ||
/// - Note: A empty string will be automatically mapped to a `nil` value for the respective property of `PersonNameComponents`. | ||
public struct NameTextField<Label: View>: View { | ||
private let prompt: Text? | ||
private let label: Label | ||
private let nameComponent: WritableKeyPath<PersonNameComponents, String?> | ||
|
||
@Binding private var name: PersonNameComponents | ||
|
||
private var componentBinding: Binding<String> { | ||
Binding { | ||
name[keyPath: nameComponent] ?? "" | ||
} set: { newValue in | ||
name[keyPath: nameComponent] = newValue.isEmpty ? nil : newValue | ||
} | ||
} | ||
|
||
private var contentType: UITextContentType? { | ||
switch nameComponent { | ||
case \.namePrefix: | ||
return .namePrefix | ||
case \.nameSuffix: | ||
return .nameSuffix | ||
case \.givenName: | ||
return .givenName | ||
case \.middleName: | ||
return .middleName | ||
case \.familyName: | ||
return .familyName | ||
case \.nickname: | ||
return .nickname | ||
default: | ||
return .name // general, catch all content type | ||
} | ||
} | ||
|
||
|
||
public var body: some View { | ||
TextField(text: componentBinding, prompt: prompt) { | ||
label | ||
} | ||
.autocorrectionDisabled() | ||
.textInputAutocapitalization(.words) | ||
.textContentType(contentType) | ||
} | ||
|
||
|
||
/// Creates a name text field with an optional prompt. | ||
/// - Parameters: | ||
/// - label: A localized title of the text field, describing its purpose. | ||
/// - 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. | ||
public init( | ||
_ label: LocalizedStringResource, | ||
name: Binding<PersonNameComponents>, | ||
for component: WritableKeyPath<PersonNameComponents, String?>, | ||
prompt: Text? = nil | ||
) where Label == Text { | ||
self.init(name: name, for: component, prompt: prompt) { | ||
Text(label) | ||
} | ||
} | ||
|
||
/// Creates a name text field with an optional prompt. | ||
/// - 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. | ||
/// - label: A view that describes the purpose of the text field. | ||
public init( | ||
name: Binding<PersonNameComponents>, | ||
for component: WritableKeyPath<PersonNameComponents, String?>, | ||
prompt: Text? = nil, | ||
@ViewBuilder label: () -> Label | ||
) { | ||
self._name = name | ||
self.nameComponent = component | ||
self.prompt = prompt | ||
self.label = label() | ||
} | ||
} | ||
|
||
|
||
#if DEBUG | ||
#Preview { | ||
@State var name = PersonNameComponents() | ||
return List { | ||
NameTextField(name: $name, for: \.givenName) { | ||
Text(verbatim: "enter first name") | ||
} | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"sourceLanguage" : "en", | ||
"strings" : { | ||
|
||
}, | ||
"version" : "1.0" | ||
} |
6 changes: 6 additions & 0 deletions
6
Sources/SpeziPersonalInfo/Resources/Localizable.xcstrings.license
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
|
||
This source file is part of the Stanford Spezi open-source project | ||
|
||
SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
|
||
SPDX-License-Identifier: MIT |
24 changes: 24 additions & 0 deletions
24
Sources/SpeziPersonalInfo/SpeziPersonalInfo.docc/SpeziPersonalInfo.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# ``SpeziPersonalInfo`` | ||
|
||
A SpeziViews target that provides a common set of SwiftUI views and related functionality for managing personal information. | ||
|
||
<!-- | ||
This source file is part of the Spezi open-source project | ||
SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
SPDX-License-Identifier: MIT | ||
--> | ||
|
||
## Topics | ||
|
||
### Person Name | ||
|
||
- ``NameTextField`` | ||
- ``NameFieldRow`` | ||
|
||
### User Profile | ||
|
||
- ``UserProfileView`` |
Oops, something went wrong.