I'm trying to display the total of the Depot Values. One depot can have many stocks.
My Overview Page has a List of Depots and should have a Text with the total value of all depots.
I get the value of one depot as following: (connected in One-To-Many-Relationship)
let sum = depot.aktienArray.map { $0.a_purchValue }.reduce(0, )
Portfolio View:
struct Portfolio: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Depot.d_name, ascending: true)],
animation: .default)
private var depots: FetchedResults<Depot>
// General States
@State private var showAddDepotView: Bool = false
// @State private var //left header button
@State private var isShowingConfirmation: Bool = false
@State private var isShowingMenu: Bool = false
@State private var navigateTo: AnyView?
@State private var isNavActive = false
@State private var depotToDelete: Depot?
@State var PortVal: Double = 0
@State var sum: Double = 0
var body: some View {
NavigationView {
GeometryReader { g in
VStack(spacing: 0) {
//MARK: DEPOT OVERVIEW
VStack {
VStack(alignment: .leading) {
HStack {
Text("Portfolio Value")
.font(.system(size: g.size.width / 22))
.foregroundColor(Color.gray)
Spacer()
Text("Details")
.foregroundColor(Color.white)
.font(.system(size: g.size.width / 22))
Image(systemName: "chevron.right")
.foregroundColor(Color.white)
.padding(.leading, g.size.width / -100)
}
.padding(.horizontal, g.size.width / 40)
.padding(.bottom, g.size.height / -45)
.padding(.top, g.size.height / 100)
HStack(alignment: .top) {
Text("\(PortVal as NSNumber, formatter: formatter) €")
.bold()
.foregroundColor(Color.blue)
.minimumScaleFactor(0.4)
.font(.system(size: g.size.width / 10))
.lineLimit(1)
Text(" 22,3 %")
.bold()
.foregroundColor(Color.green)
.font(.system(size: g.size.width / 20))
}
.padding(.horizontal, g.size.width / 40)
.padding(.bottom, g.size.height / 800)
HStack {
VStack(alignment: .leading) {
Text("Liquidität")
.font(.system(size: g.size.width / 22))
.foregroundColor(Color.gray)
.padding(.bottom, g.size.height / -75)
Text("339.830,87 €")
.bold()
.foregroundColor(Color.blue)
.minimumScaleFactor(0.4)
.font(.system(size: g.size.width / 14))
.lineLimit(1)
}
Spacer()
VStack(alignment: .center) {
Text("Yield")
.font(.system(size: g.size.width / 22))
.foregroundColor(Color.gray)
.padding(.bottom, g.size.height / -75)
Text("5,80 %")
.bold()
.foregroundColor(Color.blue)
.font(.system(size: g.size.width / 14))
}
Spacer()
}
.padding(.leading, g.size.width / 40)
.padding(.bottom, g.size.height / 120)
}
.frame(height: UIScreen.main.bounds.height / 5.5)
.frame(maxWidth: .infinity)
.background(RoundedRectangle(cornerRadius: 15).fill(Color.white.opacity(0.1)))
}
.padding(.horizontal, g.size.width / 30)
.padding(.vertical, g.size.height / 65)
.padding(.bottom, g.size.height / 150)
//
//MARK: DEPOT LIST
// DEPOT LIST TITLE
VStack(spacing: 0) {
Divider()
HStack {
Text("Depots")
.bold()
.font(.system(size: g.size.width / 12))
.foregroundColor(.white)
Spacer()
}
.padding(.horizontal, g.size.width / 30)
.padding(.top, g.size.width / 40)
.padding(.bottom, g.size.width / 100)
Divider()
.overlay(.white)
.padding(.horizontal, g.size.width / 30)
}
//DEPOT LIST
VStack(alignment: .leading) {
ScrollView(showsIndicators: false) {
Color.clear
.padding(.bottom, g.size.height / -200)
ForEach(depots) { depot in
let sum = depot.aktienArray.map { $0.a_purchValue }.reduce(0, )
ZStack {
NavigationLink(destination: DepotDetail(depot: depot)) {
VStack(alignment: .leading, spacing: 6) {
HStack {
VStack (alignment: .leading){
HStack (alignment: .bottom) {
Text(depot.d_name ?? "")
.font(.system(size: 22))
.foregroundColor(Color.GrayD)
.bold()
Text("Depot-Nr.: \(depot.d_nr as NSNumber, formatter: formatter2)")
.foregroundColor(.gray)
.font(.system(size: 16))
Spacer()
}
Text("Eröffnet: \(depot.d_createDate ?? Date(), formatter: formatterD)")
.font(.system(size: 15))
.foregroundColor(.GrayD)
}
}.padding(.bottom, -0.5)
.padding(.top, 40)
HStack {
VStack (alignment: .leading) {
Text("Depot Value")
.font(.system(size: 18))
.foregroundColor(Color.gray)
if sum == 0 {
Text("0,00 €")
.bold()
.foregroundColor(Color.GrayD)
.minimumScaleFactor(0.32)
.font(.system(size: g.size.width / 4))
.lineLimit(1)
.padding(.bottom, -5)
} else {
Text("\(sum as NSNumber, formatter: formatter) €")
.bold()
.foregroundColor(Color.GrayD)
.minimumScaleFactor(0.32)
.font(.system(size: g.size.width / 4))
.lineLimit(1)
.padding(.bottom, -5)
}
Text("Performance")
.font(.system(size: 18))
.foregroundColor(Color.gray)
HStack {
Text("360,00 €")
.bold()
.foregroundColor(Color.green)
.font(.system(size: 25))
.lineLimit(1)
Text(" 3,6 %")
.bold()
.foregroundColor(Color.green)
.font(.system(size: 18))
}
}
Spacer()
VStack(alignment: .leading) {
Text("Yield")
.font(.system(size: 18))
.foregroundColor(Color.gray)
Text("5,80 %")
.bold()
.foregroundColor(Color.GrayD)
.font(.system(size: 25))
Spacer()
Text("FSA")
.font(.system(size: 18))
.foregroundColor(Color.gray)
Text("\(depot.d_fsa as NSNumber, formatter: formatter) €")
.bold()
.foregroundColor(Color.GrayD)
.font(.system(size: 25))
}
}
Spacer()
Spacer()
Spacer()
}
.padding(.horizontal, g.size.width / 40)
.frame(height: UIScreen.main.bounds.height / 4.7)
.frame(maxWidth: .infinity)
.background(RoundedRectangle(cornerRadius: 15).fill(Color.white))
}.buttonStyle(FlatLinkStyle())
Menu {
Button {
self.navigateTo = AnyView(EditDepot(depot: depot))
self.isNavActive = true
} label: {
Label("Bearbeiten", systemImage: "pencil")
}
Button(role: .destructive) {
depotToDelete = depot
isShowingConfirmation = true
} label: {
Label("Löschen", systemImage: "trash")
}
} label: {
Image(systemName: "ellipsis.circle").foregroundColor(.GrayD)
}
.padding(.bottom, g.size.height / 5.75)
.padding(.leading, g.size.width / 1.25)
.confirmationDialog("Depot", isPresented: $isShowingConfirmation, titleVisibility: .visible) {
if let depot = depotToDelete {
Text("Depot \"\(depot.d_name ?? "")\" wirklich löschen?")
}
Button(role: .destructive) {
if let depot = depotToDelete {
deleteDepot(depot: depot)
}
} label: {
if let depot = depotToDelete {
Text("\"\(depot.d_name ?? "")\" wirklich löschen?")
}
}
}
.background(
NavigationLink(destination: self.navigateTo, isActive: $isNavActive) {
EmptyView()
}
)
}
.onAppear {
PortVal = 0
PortVal sum
}
// .onChange(of: sum, perform: { _ in
// PortVal = 0
// PortVal = sum
//
// })
}
// DEPOT ADD BUTTON
VStack {
HStack {
Button {
showAddDepotView.toggle()
} label: {
HStack {
Image(systemName: "plus")
Text("Depot hinzufügen")
.font(.headline)
}
.foregroundColor(.white)
}
}
.frame(height: UIScreen.main.bounds.height / 20)
.frame(maxWidth: .infinity)
.background(RoundedRectangle(cornerRadius: 15).fill(Color.white.opacity(0.1)))
}
.padding(.top, g.size.height / 50)
}
}
.padding(.horizontal, g.size.width / 30)
}
.background(Color.GrayD.ignoresSafeArea())
.fullScreenCover(isPresented: $showAddDepotView, content: AddDepot.init)
.preferredColorScheme(.dark)
}
.navigationTitle("")
.navigationBarHidden(true)
}
}
private func deleteDepot(depot: Depot) {
withAnimation {
viewContext.delete(depot)
do {
try viewContext.save()
} catch {
print(error)
}
}
}
}
DepotDetail View:
import SwiftUI
struct DepotDetail: View {
@Environment(\.managedObjectContext) private var viewContext
@Environment(\.presentationMode) var presentationMode
// CoreData States
@StateObject var depot: Depot
@State private var aktieToDelete: AktieKauf?
@State private var a_name: String = ""
@State private var a_industry: String = ""
@State private var a_segment: String = ""
@State private var a_shares: Double = 0
@State private var a_purchPrice: Double = 0
@State private var a_purchValue: Double = 0
@State private var a_expDividend: Double = 0
@State private var a_fees: Double = 0
@State private var a_ertrag: Double = 0
@State private var a_purchDate: Date = Date()
// General States
@State private var showAddStockView: Bool = false
@State private var isShowingConfirmation: Bool = false
@State private var navigateTo: AnyView?
@State private var isNavActive = false
var body: some View {
GeometryReader { g in
VStack(spacing: 0) {
Text("\(PortfolioValue() as NSNumber, formatter: formatter) €")
// Image(systemName: "ellipsis")
//MARK: BESTAND
VStack(alignment: .leading) {
ScrollView(showsIndicators: false) {
Color.clear
.padding(.bottom, g.size.height / -200)
ForEach(depot.aktienArray) { aktie in
ZStack {
//NavigationLink(destination: StockDetail(depot: depot)) {
VStack(alignment: .leading, spacing: 6) {
HStack {
VStack (alignment: .leading){
HStack (alignment: .bottom) {
Text(aktie.unwrappedName)
.font(.system(size: 22))
.foregroundColor(Color.GrayD)
.bold()
Text("\(aktie.a_purchValue)")
.foregroundColor(.black)
Text(aktie.a_industry ?? "").foregroundColor(.black)
Spacer()
}
Text("Gekauft: \(aktie.a_purchDate ?? Date(), style: .date)")
.foregroundColor(.GrayD)
}
}
.padding(.bottom, -0.5)
.padding(.top, 40)
HStack {
VStack (alignment: .leading) {
Text("Depot Value")
.foregroundColor(Color.gray)
Text("360.000,00 €")
.bold()
.font(.system(size: g.size.width / 4))
.lineLimit(1)
}
Spacer()
VStack(alignment: .center) {
Text("Yield")
.font(.system(size: 18))
.foregroundColor(Color.gray)
Text("5,80 %")
.bold()
.foregroundColor(Color.GrayD)
}
}
HStack {
VStack(alignment: .leading) {
Text("Performance")
.font(.system(size: 18))
HStack {
Text("360,00 €")
.bold()
.font(.system(size: 25))
.lineLimit(1)
Text(" 3,6 %")
.bold()
.foregroundColor(Color.green)
}
}
}
Spacer()
Spacer()
Spacer()
}
.padding(.horizontal, g.size.width / 40)
.frame(height: UIScreen.main.bounds.height / 4.7)
.frame(maxWidth: .infinity)
.background(RoundedRectangle(cornerRadius: 15).fill(Color.white))
//}
//.buttonStyle(FlatLinkStyle())
Menu {
Button {
self.navigateTo = AnyView(EditDepot(depot: depot))
self.isNavActive = true
} label: {
Label("Bearbeiten", systemImage: "pencil")
}
Button(role: .destructive) {
aktieToDelete = aktie
isShowingConfirmation = true
} label: {
Label("Löschen", systemImage: "trash")
}
} label: {
Image(systemName: "ellipsis.circle").foregroundColor(.GrayD)
}
.padding(.bottom, g.size.height / 5.75)
.padding(.leading, g.size.width / 1.25)
.confirmationDialog("Aktie", isPresented: $isShowingConfirmation, titleVisibility: .visible) {
if let aktie = aktieToDelete {
Text("Aktie \"\(aktie.a_name ?? "?")\" wirklich löschen?")
}
Button(role: .destructive) {
if let aktie = aktieToDelete {
deleteStock(aktieKauf: aktie)
}
} label: {
if let aktie = aktieToDelete {
Text("\"\(aktie.a_name ?? "?")\" wirklich löschen?")
}
}
}
//.background(
//NavigationLink(destination: self.navigateTo, //isActive: $isNavActive) {
// EmptyView()
// }
//)
}
}
}
}
.padding(.horizontal, g.size.width / 30)
//
Spacer()
}
.background(Color.GrayD.ignoresSafeArea())
.fullScreenCover(isPresented: $showAddStockView) {
AddStock(depot: depot)
}
.preferredColorScheme(.dark)
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
private func addStock() {
withAnimation {
let newAktie = AktieKauf(context: viewContext)
newAktie.a_name = a_name
newAktie.a_industry = a_industry
newAktie.a_segment = a_segment
newAktie.a_shares = a_shares
newAktie.a_purchPrice = a_purchPrice
newAktie.a_purchValue = a_purchValue
newAktie.a_expDividend = a_expDividend
newAktie.a_fees = a_fees
newAktie.a_ertrag = a_ertrag
newAktie.a_purchDate = a_purchDate
depot.addToAktieKaufRel(newAktie)
PersistenceController.shared.saveContext()
}
}
private func deleteStock(aktieKauf: AktieKauf) {
withAnimation {
viewContext.delete(aktieKauf)
do {
try viewContext.save()
} catch {
print(error)
}
}
}
func deleteAktie(at offsets: IndexSet) {
withAnimation {
for index in offsets {
let aktie = depot.aktienArray[index]
viewContext.delete(aktie)
PersistenceController.shared.saveContext()
}
}
}
private func PortfolioValue() -> Double {
var portfolioValue: Double = 0
for item in depot.aktienArray {
portfolioValue = item.a_purchValue
}
return portfolioValue
}
}
CodePudding user response:
With SwiftUI you want to create a ViewModel that represents something that's the formatted version of your model. Something that's as easy as possible to map to views. You don't want to be doing calculation and logic within a View's body.
So for example:
extension Depot {
func sumOfStuff() -> Double {
aktienArray.map(\.a_purchValue).reduce(0, )
}
}
That would be your model, then your view model might be that value stringified for presentation.
extension DepotViewModel {
var presentableSumOfStuff: String {
if depot.sumOfStuff() == 0 { return "0" } else { ... }
The problem is this "normal swift code" inside your ViewBuilder block:
ForEach(depots) { depot in
let sum = depot.aktienArray.map { $0.a_purchValue }.reduce(0, ) // This should not be here
ZStack {
Within a ViewBuilder context, you can't put normal swift code, it should just be a list of Views.
If you set up your ViewModel right, then you don't have complex logic in the view like instead of this:
if sum == 0 {
Text("0,00 €")
.bold()
.foregroundColor(Color.GrayD)
.minimumScaleFactor(0.32)
.font(.system(size: g.size.width / 4))
.lineLimit(1)
.padding(.bottom, -5)
} else {
Text("\(sum as NSNumber, formatter: formatter) €")
.bold()
.foregroundColor(Color.GrayD)
.minimumScaleFactor(0.32)
.font(.system(size: g.size.width / 4))
.lineLimit(1)
.padding(.bottom, -5)
}
You would have
Text($0.presentableSumOfStuff)
.bold()
.foregroundColor(Color.GrayD)
.minimumScaleFactor(0.32)
.font(.system(size: g.size.width / 4))
.lineLimit(1)
.padding(.bottom, -5)