fix: wrap iOS 17+ APIs in @available checks for iOS 16 compat

ContentUnavailableView and .symbolEffect(.pulse) require iOS 17+
but deployment target is iOS 16. Wrapped all usages in
if #available(iOS 17.0, *) with VStack-based fallbacks for iOS 16.

Files fixed:
- ScanView.swift (4 ContentUnavailableView + 1 symbolEffect)
- QRScannerView.swift (1 ContentUnavailableView)
- BusinessListView.swift (2 ContentUnavailableView)
This commit is contained in:
Schwifty 2026-03-22 19:20:46 +00:00
parent fe2ee59930
commit 2496cab7f3
3 changed files with 154 additions and 37 deletions

View file

@ -14,19 +14,51 @@ struct BusinessListView: View {
if isLoading {
ProgressView("Loading businesses…")
} else if let error = errorMessage {
ContentUnavailableView {
Label("Error", systemImage: "exclamationmark.triangle")
} description: {
Text(error)
} actions: {
Button("Retry") { Task { await loadBusinesses() } }
if #available(iOS 17.0, *) {
ContentUnavailableView {
Label("Error", systemImage: "exclamationmark.triangle")
} description: {
Text(error)
} actions: {
Button("Retry") { Task { await loadBusinesses() } }
}
} else {
VStack(spacing: 12) {
Image(systemName: "exclamationmark.triangle")
.font(.system(size: 48))
.foregroundStyle(.orange)
Text("Error")
.font(.headline)
Text(error)
.font(.subheadline)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
Button("Retry") { Task { await loadBusinesses() } }
.buttonStyle(.borderedProminent)
}
.padding()
}
} else if businesses.isEmpty {
ContentUnavailableView(
"No Businesses",
systemImage: "building.2",
description: Text("You don't have any businesses yet.")
)
if #available(iOS 17.0, *) {
ContentUnavailableView(
"No Businesses",
systemImage: "building.2",
description: Text("You don't have any businesses yet.")
)
} else {
VStack(spacing: 12) {
Image(systemName: "building.2")
.font(.system(size: 48))
.foregroundStyle(.secondary)
Text("No Businesses")
.font(.headline)
Text("You don't have any businesses yet.")
.font(.subheadline)
.foregroundStyle(.secondary)
}
.frame(maxHeight: .infinity)
.padding()
}
} else {
List(businesses) { business in
Button {

View file

@ -141,18 +141,40 @@ struct QRScannerView: View {
}
}
@ViewBuilder
private var cameraPermissionDenied: some View {
ContentUnavailableView {
Label("Camera Access Required", systemImage: "camera.fill")
} description: {
Text("Open Settings and enable camera access for Payfrit Beacon.")
} actions: {
Button("Open Settings") {
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
if #available(iOS 17.0, *) {
ContentUnavailableView {
Label("Camera Access Required", systemImage: "camera.fill")
} description: {
Text("Open Settings and enable camera access for Payfrit Beacon.")
} actions: {
Button("Open Settings") {
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
}
.buttonStyle(.borderedProminent)
}
.buttonStyle(.borderedProminent)
} else {
VStack(spacing: 16) {
Image(systemName: "camera.fill")
.font(.system(size: 48))
.foregroundStyle(.secondary)
Text("Camera Access Required")
.font(.headline)
Text("Open Settings and enable camera access for Payfrit Beacon.")
.font(.subheadline)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
Button("Open Settings") {
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}

View file

@ -129,27 +129,62 @@ struct ScanView: View {
.padding()
}
@ViewBuilder
private var selectServicePointPrompt: some View {
ContentUnavailableView(
"Select a Service Point",
systemImage: "mappin.and.ellipse",
description: Text("Choose or create a service point (table) to provision a beacon for.")
)
if #available(iOS 17.0, *) {
ContentUnavailableView(
"Select a Service Point",
systemImage: "mappin.and.ellipse",
description: Text("Choose or create a service point (table) to provision a beacon for.")
)
} else {
VStack(spacing: 12) {
Image(systemName: "mappin.and.ellipse")
.font(.system(size: 48))
.foregroundStyle(.secondary)
Text("Select a Service Point")
.font(.headline)
Text("Choose or create a service point (table) to provision a beacon for.")
.font(.subheadline)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
}
.frame(maxHeight: .infinity)
.padding()
}
}
// MARK: - Namespace Loading
@ViewBuilder
private var namespaceLoadingView: some View {
VStack(spacing: 16) {
if isLoadingNamespace {
ProgressView("Allocating beacon namespace…")
} else {
ContentUnavailableView {
Label("Namespace Error", systemImage: "exclamationmark.triangle")
} description: {
Text(errorMessage ?? "Failed to allocate beacon namespace")
} actions: {
Button("Retry") { Task { await loadNamespace() } }
if #available(iOS 17.0, *) {
ContentUnavailableView {
Label("Namespace Error", systemImage: "exclamationmark.triangle")
} description: {
Text(errorMessage ?? "Failed to allocate beacon namespace")
} actions: {
Button("Retry") { Task { await loadNamespace() } }
}
} else {
VStack(spacing: 12) {
Image(systemName: "exclamationmark.triangle")
.font(.system(size: 48))
.foregroundStyle(.orange)
Text("Namespace Error")
.font(.headline)
Text(errorMessage ?? "Failed to allocate beacon namespace")
.font(.subheadline)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
Button("Retry") { Task { await loadNamespace() } }
.buttonStyle(.borderedProminent)
}
.padding()
}
}
}
@ -245,11 +280,26 @@ struct ScanView: View {
.padding()
if bleManager.discoveredBeacons.isEmpty && !bleManager.isScanning {
ContentUnavailableView(
"No Beacons Found",
systemImage: "antenna.radiowaves.left.and.right.slash",
description: Text("Tap Scan to search for nearby beacons")
)
if #available(iOS 17.0, *) {
ContentUnavailableView(
"No Beacons Found",
systemImage: "antenna.radiowaves.left.and.right.slash",
description: Text("Tap Scan to search for nearby beacons")
)
} else {
VStack(spacing: 12) {
Image(systemName: "antenna.radiowaves.left.and.right.slash")
.font(.system(size: 48))
.foregroundStyle(.secondary)
Text("No Beacons Found")
.font(.headline)
Text("Tap Scan to search for nearby beacons")
.font(.subheadline)
.foregroundStyle(.secondary)
}
.frame(maxHeight: .infinity)
.padding()
}
} else {
List(bleManager.discoveredBeacons) { beacon in
Button {
@ -274,7 +324,7 @@ struct ScanView: View {
Image(systemName: "light.beacon.max")
.font(.system(size: 64))
.foregroundStyle(.orange)
.symbolEffect(.pulse)
.modifier(PulseEffectModifier())
Text("Beacon is Flashing")
.font(.title2.bold())
@ -768,3 +818,16 @@ struct BeaconRow: View {
}
}
// MARK: - iOS 16/17 Compatibility
/// Applies `.symbolEffect(.pulse)` on iOS 17+, no-op on iOS 16
private struct PulseEffectModifier: ViewModifier {
func body(content: Content) -> some View {
if #available(iOS 17.0, *) {
content.symbolEffect(.pulse)
} else {
content
}
}
}