Updated the Sample app and Widget targets in the Xcode project to make the app works for visionOS.

This commit is contained in:
2026-07-09 19:31:44 +02:00
parent 80bffd7aae
commit 1e11c4ae5a
4 changed files with 86 additions and 56 deletions
+6 -9
View File
@@ -11,7 +11,7 @@
02870A292FF7FF530079EA3A /* Features in Frameworks */ = {isa = PBXBuildFile; productRef = 02870A282FF7FF530079EA3A /* Features */; }; 02870A292FF7FF530079EA3A /* Features in Frameworks */ = {isa = PBXBuildFile; productRef = 02870A282FF7FF530079EA3A /* Features */; };
0296F7F02FFAB1B600D2C5FC /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0296F7EF2FFAB1B600D2C5FC /* WidgetKit.framework */; }; 0296F7F02FFAB1B600D2C5FC /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0296F7EF2FFAB1B600D2C5FC /* WidgetKit.framework */; };
0296F7F22FFAB1B600D2C5FC /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0296F7F12FFAB1B600D2C5FC /* SwiftUI.framework */; }; 0296F7F22FFAB1B600D2C5FC /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0296F7F12FFAB1B600D2C5FC /* SwiftUI.framework */; };
0296F7FF2FFAB1B600D2C5FC /* AttendiWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0296F7EE2FFAB1B600D2C5FC /* AttendiWidgetExtension.appex */; platformFilters = (ios, xros, ); settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 0296F7FF2FFAB1B600D2C5FC /* AttendiWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0296F7EE2FFAB1B600D2C5FC /* AttendiWidgetExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
02D46AFE2FFAE77500266643 /* Features in Frameworks */ = {isa = PBXBuildFile; productRef = 02D46AFD2FFAE77500266643 /* Features */; }; 02D46AFE2FFAE77500266643 /* Features in Frameworks */ = {isa = PBXBuildFile; productRef = 02D46AFD2FFAE77500266643 /* Features */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@@ -342,10 +342,7 @@
}; };
0296F7FE2FFAB1B600D2C5FC /* PBXTargetDependency */ = { 0296F7FE2FFAB1B600D2C5FC /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
platformFilters = ( platformFilter = ios;
ios,
xros,
);
target = 0296F7ED2FFAB1B600D2C5FC /* AttendiWidgetExtension */; target = 0296F7ED2FFAB1B600D2C5FC /* AttendiWidgetExtension */;
targetProxy = 0296F7FD2FFAB1B600D2C5FC /* PBXContainerItemProxy */; targetProxy = 0296F7FD2FFAB1B600D2C5FC /* PBXContainerItemProxy */;
}; };
@@ -833,7 +830,7 @@
SDKROOT = auto; SDKROOT = auto;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator xros xrsimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES;
@@ -841,7 +838,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 6.0; SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2,4,7"; TARGETED_DEVICE_FAMILY = "1,2";
WATCHOS_DEPLOYMENT_TARGET = 26.0; WATCHOS_DEPLOYMENT_TARGET = 26.0;
XROS_DEPLOYMENT_TARGET = 26.0; XROS_DEPLOYMENT_TARGET = 26.0;
}; };
@@ -925,14 +922,14 @@
SDKROOT = auto; SDKROOT = auto;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator xros xrsimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 6.0; SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2,4,7"; TARGETED_DEVICE_FAMILY = "1,2";
WATCHOS_DEPLOYMENT_TARGET = 26.0; WATCHOS_DEPLOYMENT_TARGET = 26.0;
XROS_DEPLOYMENT_TARGET = 26.0; XROS_DEPLOYMENT_TARGET = 26.0;
}; };
@@ -45,10 +45,14 @@ public struct NotificationLabelStyle: LabelStyle {
} }
.padding(.vertical, Constant.Padding.vertical) .padding(.vertical, Constant.Padding.vertical)
.padding(.horizontal, Constant.Padding.horizontal) .padding(.horizontal, Constant.Padding.horizontal)
#if os(visionOS)
.glassBackgroundEffect(in: .capsule)
#else
.glassEffect( .glassEffect(
.regular, .regular,
in: .capsule in: .capsule
) )
#endif
} }
} }
@@ -3,7 +3,8 @@ import SwiftUI
/// The button style for the controls of a ``RecordingView``. /// The button style for the controls of a ``RecordingView``.
/// ///
/// The style renders the button's label in the primary foreground color over a padded, circular Liquid Glass background tinted by /// The style renders the button's label in the primary foreground color over a padded, circular Liquid Glass background tinted by
/// the color given at initialization that reacts fluidly to presses. The whole button dims while disabled, and both the label and its /// the color given at initialization that reacts fluidly to presses. On visionOS, where Liquid Glass is unavailable, the background is
/// the platform's own glass material overlaid with the tint color. The whole button dims while disabled, and both the label and its
/// padding scale relative to the current dynamic type size. /// padding scale relative to the current dynamic type size.
struct RecordingButtonStyle: ButtonStyle { struct RecordingButtonStyle: ButtonStyle {
@@ -52,6 +53,14 @@ struct RecordingButtonStyle: ButtonStyle {
.padding(padding) .padding(padding)
.scaledToFit() .scaledToFit()
.foregroundStyle(.primary.opacity(opacity)) .foregroundStyle(.primary.opacity(opacity))
#if os(visionOS)
.background(
tintColor.opacity(Constant.Opacity.tint),
in: .circle
)
.glassBackgroundEffect(in: .circle)
.opacity(opacity)
#else
.glassEffect( .glassEffect(
.regular .regular
.tint(tintColor) .tint(tintColor)
@@ -59,6 +68,7 @@ struct RecordingButtonStyle: ButtonStyle {
in: .circle in: .circle
) )
.opacity(opacity) .opacity(opacity)
#endif
} }
} }
@@ -108,6 +118,8 @@ private enum Constant {
static let disabled: Double = 0.5 static let disabled: Double = 0.5
/// The opacity of an enabled button. /// The opacity of an enabled button.
static let enabled: Double = 1 static let enabled: Double = 1
/// The opacity of the tint color laid over the glass background on visionOS.
static let tint: Double = 0.5
} }
/// The padding constants. /// The padding constants.
@@ -81,54 +81,13 @@ public struct RecordingView: View {
.accessibilityValue(model.textTimerAccessible) .accessibilityValue(model.textTimerAccessible)
} }
#if os(visionOS)
controls
#else
GlassEffectContainer { GlassEffectContainer {
HStack( controls
spacing: Constant.Spacing.stack
) {
if model.shouldShowActions {
Button {
model.pressedDiscard()
} label: {
Image.Icon.discard
.resizable()
}
.buttonStyle(.recording(
tintColor: .red
))
.accessibilityLabel(.viewRecordingButtonDiscardLabel)
}
Button {
model.pressedMain()
} label: {
Image(model.iconMain)
.resizable()
}
.buttonStyle(.recording(
tintColor: .yellow
))
.disabled(model.shouldDisableMain)
.accessibilityLabel(model.labelMain)
if model.shouldShowActions {
Button {
model.pressedSend()
} label: {
if let icon = model.iconSend {
Image(icon)
.resizable()
} else {
ProgressView()
}
}
.buttonStyle(.recording(
tintColor: .green
))
.disabled(model.isProcessing)
.accessibilityLabel(model.labelSend)
}
}
} }
#endif
} }
.animation( .animation(
.easeInOut, .easeInOut,
@@ -174,6 +133,64 @@ public struct RecordingView: View {
} }
// MARK: - Subviews
private extension RecordingView {
// MARK: Computed
/// The main, discard, and send buttons controlling the recording flow, laid out in a horizontal stack.
var controls: some View {
HStack(
spacing: Constant.Spacing.stack
) {
if model.shouldShowActions {
Button {
model.pressedDiscard()
} label: {
Image.Icon.discard
.resizable()
}
.buttonStyle(.recording(
tintColor: .red
))
.accessibilityLabel(.viewRecordingButtonDiscardLabel)
}
Button {
model.pressedMain()
} label: {
Image(model.iconMain)
.resizable()
}
.buttonStyle(.recording(
tintColor: .yellow
))
.disabled(model.shouldDisableMain)
.accessibilityLabel(model.labelMain)
if model.shouldShowActions {
Button {
model.pressedSend()
} label: {
if let icon = model.iconSend {
Image(icon)
.resizable()
} else {
ProgressView()
}
}
.buttonStyle(.recording(
tintColor: .green
))
.disabled(model.isProcessing)
.accessibilityLabel(model.labelSend)
}
}
}
}
// MARK: - Constants // MARK: - Constants
/// The constant values used across the view. /// The constant values used across the view.