[英]How to refresh Core Data array when user enters new view with SwiftUI?
我有 3 個視圖。 內容視圖、培訓視圖和培訓列表視圖。 我想列出 Core Data 中的練習,但我也想在不更改數據的情況下進行一些更改。
在內容視圖中; 我正在嘗試使用 CoreData 獲取數據
struct ContentView: View {
// MARK: - PROPERTY
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Training.timestamp, ascending: false)],
animation: .default)
private var trainings: FetchedResults<Training>
@State private var showingAddProgram: Bool = false
// FETCHING DATA
// MARK: - FUNCTION
// MARK: - BODY
var body: some View {
NavigationView {
Group {
VStack {
HStack {
Text("Your Programs")
Spacer()
Button(action: {
self.showingAddProgram.toggle()
}) {
Image(systemName: "plus")
}
.sheet(isPresented: $showingAddProgram) {
AddProgramView()
}
} //: HSTACK
.padding()
List {
ForEach(trainings) { training in
TrainingListView(training: training)
}
} //: LIST
Spacer()
} //: VSTACK
} //: GROUP
.navigationTitle("Good Morning")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
print("test")
}) {
Image(systemName: "key")
}
}
} //: TOOLBAR
.onAppear() {
}
} //: NAVIGATION
}
private func showId(training: Training) {
guard let id = training.id else { return }
print(id)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
在培訓視圖中; 我正在以數組列表的形式進行練習,並且正在進入 TrainingListView。
import SwiftUI
struct TrainingView: View {
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
@State var training: Training
@State var exercises: [Exercise]
@State var tempExercises: [Exercise] = [Exercise]()
@State var timeRemaining = 0
@State var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
@State var isTimerOn = false
var body: some View {
VStack {
HStack {
Text("\(training.name ?? "")")
Spacer()
Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Text("Finish")
}
}
.padding()
ZStack {
Circle()
.fill(Color.blue)
.frame(width: 250, height: 250)
Circle()
.fill(Color.white)
.frame(width: 240, height: 240)
Text("\(timeRemaining)s")
.font(.system(size: 100))
.fontWeight(.ultraLight)
.onReceive(timer) { _ in
if isTimerOn {
if timeRemaining > 0 {
timeRemaining -= 1
} else {
isTimerOn.toggle()
stopTimer()
removeExercise()
}
}
}
}
Button(action: {
startResting()
}) {
if isTimerOn {
Text("CANCEL")
} else {
Text("GIVE A BREAK")
}
}
Spacer()
ExerciseListView(exercises: $tempExercises)
}
.navigationBarHidden(true)
.onAppear() {
updateBigTimer()
}
}
private func startResting() {
tempExercises = exercises
if let currentExercise: Exercise = tempExercises.first {
timeRemaining = Int(currentExercise.rest)
startTimer()
isTimerOn.toggle()
}
}
private func removeExercise() {
if let currentExercise: Exercise = tempExercises.first {
if Int(currentExercise.rep) == 1 {
let index = tempExercises.firstIndex(of: currentExercise) ?? 0
tempExercises.remove(at: index)
} else if Int(currentExercise.rep) > 1 {
currentExercise.rep -= 1
let index = tempExercises.firstIndex(of: currentExercise) ?? 0
tempExercises.remove(at: index)
tempExercises.insert(currentExercise, at: index)
}
updateBigTimer()
}
}
private func updateBigTimer() {
timeRemaining = Int(tempExercises.first?.rest ?? 0)
}
private func stopTimer() {
timer.upstream.connect().cancel()
}
private func startTimer() {
timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
}
}
struct TrainingView_Previews: PreviewProvider {
static var previews: some View {
TrainingView(training: Training(), exercises: [Exercise]())
}
}
在培訓列表視圖中; 我列出了所有練習。
struct TrainingListView: View {
@ObservedObject var training: Training
@Environment(\.managedObjectContext) private var managedObjectContext
var body: some View {
NavigationLink(destination: TrainingView(training: training, exercises: training.exercises?.toArray() ?? [Exercise]())) {
HStack {
Text("\(training.name ?? "")")
Text("\(training.exercises?.count ?? 0) exercises")
}
}
}
}
另外,我正在添加視頻: https://twitter.com/huseyiniyibas/status/1388571724346793986
我想要做的是,當用戶點擊任何訓練練習列表時,應該刷新。 它應該像一開始一樣再次成為x5。
我很難理解你的問題,但我想我明白了。
我的理解是這樣的:
我沒有運行您的代碼,因為我不想重新創建所有模型和核心數據文件。 我想我已經發現了問題所在。 在這里,我將解釋如何解決它:
核心數據模型是類(引用類型)。 當您傳遞類(就像您在代碼中所做的那樣)並更改它們的屬性時,您會更改原始數據。 在你的情況下,你不想要那個。
(順便說一句,作為引用類型是類的一個非常有用和強大的屬性。結構和枚舉是值類型,即它們在傳遞時被復制。原始數據是不變的。)
您有多種選擇來解決您的問題:
只需從Exercise
生成一個不同的struct
(類似於ExerciseDisplay
),然后將ExerciseDisplay
傳遞給TrainingView
。
您可以編寫Exercise
的extension
並在將model 傳遞給TrainingView
之前“復制”它。 為此,您需要實現NSCopying
協議。
extension Exercise: NSCopying {
func copy(with zone: NSZone? = nil) -> Any {
return Exercise(...)
}
}
但在此之前,我想您需要將 Codegen 更改為 Manual/None of your entry in your .xcdatamodeld
文件。 當您想要手動創建屬性時,這是必需的。 我不完全確定如何為 CoreDate model 實現NSCopying
,但這當然是可行的。
第一種方法更容易但有點丑陋。 第二個更通用和優雅,但也更高級。 只需先嘗試第一種方法,一旦您有信心再嘗試第二種方法。
更新:這簡要介紹了如何實施第一種方法:
struct ExerciseDisplay: Identifiable, Equatable {
public let id = UUID()
public let name: String
public var rep: Int
public let rest: Int
}
struct TrainingView: View {
// Other properties and states etc.
let training: Training
@State var exercises: [ExerciseDisplay] = []
init(training: Training) {
self.training = training
}
var body: some View {
VStack {
// Views
}
.onAppear() {
let stored: [Exercise] = training.exercises?.toArray() ?? []
self.exercises = stored.map { ExerciseDisplay(name: $0.name ?? "", rep: Int($0.rep), rest: Int($0.rest)) }
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.