Skip to content

Commit

Permalink
Support visionOS, macOS, tvOS and watchOS (#29)
Browse files Browse the repository at this point in the history
# Support visionOS, macOS, tvOS and watchOS

## ♻️ Current situation & Problem
This PR adds basic support for visionOS, macOS, tvOS and watchOS. Views
are updated to work on all these different platforms. Some views that
directly bridge certain UIKit functionality are only available on the
iOS platform. Minor optimizations were made that all views implemented
look good on all of the platforms.

## ⚙️ Release Notes 
 
* Added support for visionOS, macOS, tvOS and watchOS.

## 📚 Documentation

Minor updates to reflect some changes.

## ✅ Testing
 
Tests were updated to run on all of the platforms. We added CI support
for all the platforms that apply.
## 📝 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).

---------

Co-authored-by: Paul Schmiedmayer <[email protected]>
  • Loading branch information
Supereg and PSchmiedmayer authored Feb 4, 2024
1 parent 138d332 commit 7210f72
Show file tree
Hide file tree
Showing 24 changed files with 312 additions and 106 deletions.
55 changes: 51 additions & 4 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,70 @@ on:

jobs:
buildandtest:
name: Build and Test Swift Package
name: Build and Test Swift Package iOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
artifactname: SpeziViews-Package.xcresult
runsonlabels: '["macOS", "self-hosted"]'
scheme: SpeziViews-Package
buildandtestwatchos:
name: Build and Test Swift Package watchOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
artifactname: SpeziViews-Package-watchOS.xcresult
resultBundle: SpeziViews-Package-watchOS.xcresult
runsonlabels: '["macOS", "self-hosted"]'
scheme: SpeziViews-Package
destination: 'platform=watchOS Simulator,name=Apple Watch Series 9 (45mm)'
buildandtestvisionos:
name: Build and Test Swift Package visionOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
artifactname: SpeziViews-Package-visionOS.xcresult
resultBundle: SpeziViews-Package-visionOS.xcresult
runsonlabels: '["macOS", "self-hosted"]'
scheme: SpeziViews-Package
destination: 'platform=visionOS Simulator,name=Apple Vision Pro'
buildandtesttvos:
name: Build and Test Swift Package tvOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
artifactname: SpeziViews-Package-tvOS.xcresult
resultBundle: SpeziViews-Package-tvOS.xcresult
runsonlabels: '["macOS", "self-hosted"]'
scheme: SpeziViews-Package
destination: 'platform=tvOS Simulator,name=Apple TV 4K (3rd generation)'
buildandtestuitests:
name: Build and Test UI Tests
name: Build and Test UI Tests iOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
artifactname: TestApp.xcresult
runsonlabels: '["macOS", "self-hosted"]'
path: 'Tests/UITests'
scheme: TestApp
buildandtestuitestsipad:
name: Build and Test UI Tests iPadOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
artifactname: TestApp-iPad.xcresult
resultBundle: TestApp-iPad.xcresult
runsonlabels: '["macOS", "self-hosted"]'
path: 'Tests/UITests'
scheme: TestApp
destination: 'platform=iOS Simulator,name=iPad Air (5th generation)'
buildandtestuitestsvisionos:
name: Build and Test UI Tests visionOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
artifactname: TestApp-visionOS.xcresult
resultBundle: TestApp-visionOS.xcresult
runsonlabels: '["macOS", "self-hosted"]'
path: 'Tests/UITests'
scheme: TestApp
destination: 'platform=visionOS Simulator,name=Apple Vision Pro'
uploadcoveragereport:
name: Upload Coverage Report
needs: [buildandtest, buildandtestuitests]
needs: [buildandtest, buildandtestwatchos, buildandtestvisionos, buildandtesttvos, buildandtestuitests, buildandtestuitestsipad, buildandtestuitestsvisionos]
uses: StanfordSpezi/.github/.github/workflows/create-and-upload-coverage-report.yml@v2
with:
coveragereports: SpeziViews-Package.xcresult TestApp.xcresult
coveragereports: SpeziViews-Package.xcresult SpeziViews-Package-watchOS.xcresult SpeziViews-Package-visionOS.xcresult SpeziViews-Package-tvOS.xcresult TestApp.xcresult TestApp-iPad.xcresult TestApp-visionOS.xcresult
3 changes: 3 additions & 0 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ authors:
- family-names: "Aalami"
given-names: "Oliver"
orcid: "https://orcid.org/0000-0002-7799-2429"
- family-names: "Bauer"
given-names: "Andreas"
orcid: "https://orcid.org/0000-0002-1680-237X"
title: "SpeziViews"
doi: 10.5281/zenodo.7806475
url: "https://github.com/StanfordSpezi/SpeziViews"
6 changes: 5 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ let package = Package(
name: "SpeziViews",
defaultLocalization: "en",
platforms: [
.iOS(.v17)
.iOS(.v17),
.visionOS(.v1),
.tvOS(.v17),
.watchOS(.v10),
.macOS(.v14)
],
products: [
.library(name: "SpeziViews", targets: ["SpeziViews"]),
Expand Down
5 changes: 4 additions & 1 deletion Sources/SpeziPersonalInfo/Fields/NameTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// SPDX-License-Identifier: MIT
//

import SpeziViews
import SwiftUI


Expand Down Expand Up @@ -43,7 +44,7 @@ public struct NameTextField<Label: View>: View {
}
}

private var contentType: UITextContentType? {
private var contentType: TextContentType {
switch nameComponent {
case \.namePrefix:
return .namePrefix
Expand All @@ -68,7 +69,9 @@ public struct NameTextField<Label: View>: View {
label
}
.autocorrectionDisabled()
#if !os(macOS)
.textInputAutocapitalization(.words)
#endif
.textContentType(contentType)
}

Expand Down
48 changes: 35 additions & 13 deletions Sources/SpeziPersonalInfo/UserProfileView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,44 @@ public struct UserProfileView: View {
private let imageLoader: () async -> Image?

@State private var image: Image?



@Environment(\.colorScheme)
private var colorScheme

private var systemBackgroundWhite: Color {
#if os(macOS)
return Color(nsColor: .windowBackgroundColor)
#elseif os(watchOS) || os(tvOS)
return Color(uiColor: .gray)
#else
return Color(uiColor: .systemBackground)
#endif
}

private var letterCircleColor: Color {
#if os(macOS)
return .gray
#elseif os(watchOS) || os(tvOS)
return Color(uiColor: .darkGray)
#else
return Color(uiColor: .systemGray2)
#endif
}


public var body: some View {
GeometryReader { context in
ZStack {
if let image {
Circle()
.foregroundColor(Color(.systemBackground))
.foregroundColor(systemBackgroundWhite)
image.resizable()
.clipShape(Circle())
} else {
Circle()
.foregroundColor(Color(.systemGray3))
.foregroundColor(letterCircleColor)
Text(name.formatted(.name(style: .abbreviated)))
.foregroundColor(.init(UIColor.systemBackground))
.foregroundColor(colorScheme == .dark ? .secondary : systemBackgroundWhite)
.font(
.system(
size: min(context.size.height, context.size.width) * 0.45,
Expand Down Expand Up @@ -67,8 +90,8 @@ public struct UserProfileView: View {
UserProfileView(
name: PersonNameComponents(givenName: "Paul", familyName: "Schmiedmayer")
)
.frame(width: 100, height: 100)
.padding()
.frame(width: 100, height: 100)
.padding()
}

#Preview {
Expand All @@ -80,10 +103,9 @@ public struct UserProfileView: View {
familyName: "Aalami"
)
)
.frame(width: 100, height: 100)
.padding()
.background(Color(.systemBackground))
.colorScheme(.dark)
.frame(width: 100, height: 100)
.padding()
.preferredColorScheme(.dark)
}

#Preview {
Expand All @@ -94,7 +116,7 @@ public struct UserProfileView: View {
return Image(systemName: "person.crop.circle")
}
)
.frame(width: 50, height: 100)
.padding()
.frame(width: 50, height: 100)
.padding()
}
#endif
1 change: 1 addition & 0 deletions Sources/SpeziViews/SpeziViews.docc/SpeziViews.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Automatically adapt your view layouts to dynamic type sizes, device orientation,
- ``Label``
- ``LazyText``
- ``MarkdownView``
- ``TextContentType``

### Interact with the View Environment

Expand Down
26 changes: 26 additions & 0 deletions Sources/SpeziViews/Utilities/TextContentType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import SwiftUI

#if os(watchOS)
/// TextContentType typealias that is platform-agnostic.
///
/// This typealias points to [`WKTextContentType`](https://developer.apple.com/documentation/watchkit/wktextcontenttype) from WatchKit.
public typealias TextContentType = WKTextContentType // swiftlint:disable:this file_types_order
#elseif os(macOS)
/// TextContentType typealias that is platform-agnostic.
///
/// This typealias points to [`NSTextContentType`](https://developer.apple.com/documentation/appkit/nstextcontenttype) from AppKit.
public typealias TextContentType = NSTextContentType
#else
/// TextContentType typealias that is platform-agnostic.
///
/// This typealias points to [`UITextContentType`](https://developer.apple.com/documentation/uikit/uitextcontenttype) from UIKit.
public typealias TextContentType = UITextContentType
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@

import SwiftUI

#if !os(macOS) && !os(watchOS)

@available(visionOS, unavailable)
@available(tvOS, unavailable)
struct DeviceOrientationModifier: ViewModifier {
@Binding private var orientation: UIDeviceOrientation

Expand Down Expand Up @@ -51,7 +54,11 @@ extension View {
///
/// - Parameter orientation: The Binding to your `UIDeviceOrientation` state.
/// - Returns: The modified view that observes device orientation.
@available(visionOS, unavailable)
@available(tvOS, unavailable)
public func observeOrientationChanges(_ orientation: Binding<UIDeviceOrientation>) -> some View {
modifier(DeviceOrientationModifier(orientation: orientation))
}
}

#endif
4 changes: 4 additions & 0 deletions Sources/SpeziViews/Views/Drawing/CanvasView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// SPDX-License-Identifier: MIT
//

#if canImport(PencilKit) && !os(macOS)
import PencilKit
import SwiftUI

Expand Down Expand Up @@ -115,6 +116,8 @@ private struct _CanvasView: UIViewRepresentable {
/// )
/// }
/// ```
@available(macOS, unavailable)
@available(watchOS, unavailable)
public struct CanvasView: View {
/// The ``CanvasSizePreferenceKey`` enables outer views to get access to the current canvas size of the ``CanvasView``
/// using the SwiftUI preference mechanisms.
Expand Down Expand Up @@ -184,3 +187,4 @@ struct SignatureView_Previews: PreviewProvider {
}
}
#endif
#endif
28 changes: 14 additions & 14 deletions Sources/SpeziViews/Views/Layout/DescriptionGridRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,22 @@ struct DescriptionGridRow_Previews: PreviewProvider {
}
}
}
Grid(horizontalSpacing: 8, verticalSpacing: 8) {
DescriptionGridRow {
Text(verbatim: "Description")
} content: {
Text(verbatim: "Content")
}
Divider()
DescriptionGridRow {
Text(verbatim: "Description")
} content: {
Text(verbatim: "Content")
}
}

Grid(horizontalSpacing: 8, verticalSpacing: 8) {
DescriptionGridRow {
Text(verbatim: "Description")
} content: {
Text(verbatim: "Content")
}
Divider()
DescriptionGridRow {
Text(verbatim: "Description")
} content: {
Text(verbatim: "Content")
}
.padding(32)
}
.background(Color(.systemGroupedBackground))
.padding(32)
}
}
#endif
38 changes: 25 additions & 13 deletions Sources/SpeziViews/Views/Layout/DynamicHStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,26 +85,38 @@ public struct DynamicHStack<Content: View>: View {
@Environment(\.dynamicTypeSize)
private var dynamicTypeSize
@Environment(\.horizontalSizeClass)
private var horizontalSizeClass // for iPad or landscape we want to stay horizontal
private var horizontalSizeClass

#if os(iOS)
@State private var orientation = UIDevice.current.orientation
#endif

var isLandscape: Bool {
#if os(iOS)
orientation.isLandscape
#else
true
#endif
}

private var isHorizontalLayout: Bool {
horizontalSizeClass == .regular || isLandscape || dynamicTypeSize <= realignAfter
}

public var body: some View {
ZStack {
if horizontalSizeClass == .regular || orientation.isLandscape || dynamicTypeSize <= realignAfter {
HStack(alignment: horizontalAlignment, spacing: spacing) {
content
}
.preference(key: DynamicLayout.self, value: .horizontal)
} else {
VStack(alignment: verticalAlignment, spacing: spacing) {
content
}
. preference(key: DynamicLayout.self, value: .vertical)
}
let layout = isHorizontalLayout
? AnyLayout(HStackLayout(alignment: horizontalAlignment, spacing: spacing))
: AnyLayout(VStackLayout(alignment: verticalAlignment, spacing: spacing))

// Use of `AnyLayout` allows us to dynamically change the type of layout container
// without destroying the state of the subviews!
layout {
content
.preference(key: DynamicLayout.self, value: isHorizontalLayout ? .horizontal : .vertical)
}
#if os(iOS)
.observeOrientationChanges($orientation)
#endif
}


Expand Down
3 changes: 2 additions & 1 deletion Sources/SpeziViews/Views/Text/Label.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation
import SwiftUI


#if !os(macOS) && !os(watchOS)
private struct _Label: UIViewRepresentable {
let text: String
let textStyle: UIFont.TextStyle
Expand Down Expand Up @@ -124,3 +124,4 @@ struct Label_Previews: PreviewProvider {
}
}
#endif
#endif
Loading

0 comments on commit 7210f72

Please sign in to comment.