I am working with Swift Data (iOS 17 or higher only) and SwiftUI on iOS.
This question is probably only worth reading (or deciding whether to vote to close!!!) if you are highly active with this framework.
I have a View (happens to be a “Recording Editor View/Widget”) that currently is highly coupled with Swift Data in my code by using the @Bindable macro. This is very convenient and appropriate in my own project, but what if I wanted to “agnostify” this view into a Swift Package where it did not assume or impose any particular “Memo” or “Recording” type (as it would be absurd to include a top-level Data Model in a Swift Package for a view).
I think if you read this code (a simplified example with all the essential relationships shown) or attempt this yourself, you will see the dilemma.
Here is the current code with some relevant comments:
App:
import SwiftUI
import SwiftData
@main
struct RecPackage_Stack_QApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}.modelContainer(for: VoiceMemo.self)
}
}
ContentView:
import SwiftUI
import SwiftData
enum Navigation {
case main, edit
}
struct ContentView: View {
@State var showEditScreen: Bool = false
@Query private var memos: [VoiceMemo]
@Environment(.modelContext) var modelContext
@State private var memoToEdit: VoiceMemo?
var body: some View {
if !showEditScreen {
VStack {
Spacer()
Button("ADD MEMO") {
let newMemo = VoiceMemo(name: "new Memo...")
modelContext.insert(newMemo)
memoToEdit = newMemo
showEditScreen = true
}
Spacer()
ForEach(memos, id: .self) { memo in
HStack{
Button(memo.name) {
memoToEdit = memo
showEditScreen = true
}
if let rec = memo.recording {
Text(rec.attribute01.description)
Text(rec.attribute02.description)
}
}
}
Spacer()
}
.padding()
} else {
AddEditScreen(memo: memoToEdit!, showThisScreen: $showEditScreen)
}
}
}
AddEditScreen:
struct AddEditScreen: View {
@Bindable var memo:VoiceMemo
@Binding var showThisScreen:Bool
var body: some View {
VStack(alignment: .leading) {
Button("<< BACK to main") {
showThisScreen = false
}
Spacer()
TextField("hello", text: $memo.name)
Spacer()
RecordingEditorWidget(memo: memo)
}.padding(20)
}
}
RecordingEditorWidget:
// This is what I would like to extract/modularize/decouple/agnostify into a Swift Package
struct RecordingEditorWidget: View {
// Should have no knowledge of (or protocol for) this "Memo" object or the "Recording" object
@Bindable var memo:VoiceMemo
@State var isRecording: Bool = false
enum RecordingState {
case blank, recording, recorded
}
var getState: RecordingState {
if isRecording {
return .recording
} else if memo.recording == nil {
return .blank
} else {
return .recorded
}
}
var body: some View {
ZStack{
switch getState {
case .blank:
Button("Record") {
isRecording = true
// simulate recording
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
memo.recording = Recording()
isRecording = false
}
}
case .recording:
Text("Recording in progress...")
case .recorded:
// Simulated audio editor
@Bindable var recording = memo.recording!
VStack {
Spacer()
Slider(value: $recording.attribute01)
Spacer()
Slider(value: $recording.attribute02)
Spacer()
Button("Trash Recording") {
memo.deleteRecording()
}
}
}
}.background(.green.opacity(0.2))
}
}
How can this specific minimal example be made into an independent/agnostic Swift Package that only exposes appropriate bindings and/or callbacks and maintains its functionality?