#State var only changes after second button click SwiftUI - swift

Hello I apologize for my ignorance if this is an overdone or overly simple question. I am using SwiftUI and want my page to have multiple buttons that all send emails with different subject and message bodies. Everything currently works although only after the second time I click a button. The first time the subject is my initialized var ("start") regardless of button.
Another small thing, when I click the same button repeatedly it never updates until I click a different one.
Thank you very much, attached is code, I left out my mailcompose swift file as it all works as expected.
import SwiftUI
import MessageUI
import Foundation
struct ContentView: View {
#State var result: Result<MFMailComposeResult, Error>? = nil
#State private var showSheet = false
#State private var subject: String = "start"
#State private var msgBody: String = ""
let numberString = "201-228-0752"
var body: some View {
ZStack{
Color.white.ignoresSafeArea()
VStack{
Image("Icon-1024")
.resizable()
.aspectRatio(contentMode: .fit)
.clipShape(Circle())
.shadow(radius: 10)
.overlay(Circle().stroke(Color.black, lineWidth: 3))
.frame(width: 50, height: 50, alignment: .center)
.padding(.top, 10)
Text("Contact Us")
.foregroundColor(.black)
.fontWeight(.heavy)
.font(.title)
.padding()
Text("Be part of something bigger.")
.foregroundColor(.black)
.fontWeight(.medium)
.font(.system(size: 20))
.padding()
.padding(.bottom, 5)
.multilineTextAlignment(.center)
Spacer()
VStack(spacing: 0){
Button(action: {
self.subject = "General"
self.msgBody = "Hello, I need help."
self.suggestFeature()
}){
HStack {
Spacer()
Text("General help")
.font(.system(size: 25))
.fontWeight(.medium)
.foregroundColor(.white)
.padding(.top, 15)
.padding(.bottom, 15)
Spacer()
}
}
.background(Color(red: 1.00, green: 0.49, blue: 0.51))
Button(action: {
self.subject = "Technical"
self.msgBody = "Hello, I am having technical difficulties"
self.suggestFeature()
}){
HStack {
Spacer()
Text("Technical issues")
.font(.system(size: 25))
.fontWeight(.medium)
.foregroundColor(.white)
.padding(.top, 15)
.padding(.bottom, 15)
Spacer()
}
}
.background(Color(red: 0.81, green: 0.39, blue: 0.40))
Button(action: {
self.subject = "Gender"
self.msgBody = "Hello, I would like to make a gender or sexuality request."
self.suggestFeature()
}){
HStack {
Spacer()
Text("Gender & Sexuality")
.font(.system(size: 25))
.fontWeight(.medium)
.foregroundColor(.white)
.padding(.top, 15)
.padding(.bottom, 15)
Spacer()
}
}
.background(Color(red: 0.62, green: 0.29, blue: 0.30))
Button(action: {
self.subject = "Delete"
self.msgBody = "Hello, I would like to request a delete of my info."
self.suggestFeature()
}){
HStack {
Spacer()
Text("Delete info")
.font(.system(size: 25))
.fontWeight(.medium)
.foregroundColor(.white)
.padding(.top, 15)
.padding(.bottom, 15)
Spacer()
}
}
.background(Color(red: 0.45, green: 0.19, blue: 0.20))
Button(action: {
let telephone = "tel://"
let formattedString = telephone + numberString
guard let url = URL(string: formattedString) else { return }
UIApplication.shared.open(url)
}){
HStack {
Spacer()
(Text("Call ") + Text(Image(systemName: "phone.fill")))
.font(.system(size: 25))
.fontWeight(.medium)
.foregroundColor(.white)
.padding(.top, 15)
.padding(.bottom, 15)
Spacer()
}
}
.background(Color(.black))
Spacer()
Text("© Copyright Sixish, Inc.")
.padding(.bottom, 30)
.foregroundColor(.gray)
}.sheet(isPresented: $showSheet) {
MailView(result: self.$result, newSubject: self.subject, newMsgBody: self.msgBody)
}
}
}
}
func suggestFeature() {
print("You've got mail")
if MFMailComposeViewController.canSendMail() {
self.showSheet = true
} else {
print("Error sending mail")
// Alert : Unable to send the mail
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

This is a classic issue that gets run into a lot with sheet() on SwiftUI. The gist is that your sheet's content view can get rendered before it actually appears and before the #State variables' changes propagate to it.
The fix is to use sheet(item:):
struct Message : Identifiable, Hashable {
var id = UUID()
var subject : String
var body : String
}
struct ContentView: View {
//#State var result: Result<MFMailComposeResult, Error>? = nil
#State private var showSheet = false
#State private var message : Message?
let numberString = "201-228-0752"
var body: some View {
ZStack{
Color.white.ignoresSafeArea()
VStack{
Image("Icon-1024")
.resizable()
.aspectRatio(contentMode: .fit)
.clipShape(Circle())
.shadow(radius: 10)
.overlay(Circle().stroke(Color.black, lineWidth: 3))
.frame(width: 50, height: 50, alignment: .center)
.padding(.top, 10)
Text("Contact Us")
.foregroundColor(.black)
.fontWeight(.heavy)
.font(.title)
.padding()
Text("Be part of something bigger.")
.foregroundColor(.black)
.fontWeight(.medium)
.font(.system(size: 20))
.padding()
.padding(.bottom, 5)
.multilineTextAlignment(.center)
Spacer()
VStack(spacing: 0){
Button(action: {
self.message = Message(subject: "General", body: "Hello, I need help.")
self.suggestFeature()
}){
HStack {
Spacer()
Text("General help")
.font(.system(size: 25))
.fontWeight(.medium)
.foregroundColor(.white)
.padding(.top, 15)
.padding(.bottom, 15)
Spacer()
}
}
.background(Color(red: 1.00, green: 0.49, blue: 0.51))
Button(action: {
self.message = Message(subject: "Technical", body: "Hello, I am having technical difficulties")
self.suggestFeature()
}){
HStack {
Spacer()
Text("Technical issues")
.font(.system(size: 25))
.fontWeight(.medium)
.foregroundColor(.white)
.padding(.top, 15)
.padding(.bottom, 15)
Spacer()
}
}
.background(Color(red: 0.81, green: 0.39, blue: 0.40))
Button(action: {
self.message = Message(subject: "Gender", body: "Hello, I would like to make a gender or sexuality request.")
self.suggestFeature()
}){
HStack {
Spacer()
Text("Gender & Sexuality")
.font(.system(size: 25))
.fontWeight(.medium)
.foregroundColor(.white)
.padding(.top, 15)
.padding(.bottom, 15)
Spacer()
}
}
.background(Color(red: 0.62, green: 0.29, blue: 0.30))
Button(action: {
self.message = Message(subject: "Delete", body: "Hello, I would like to request a delete of my info.")
self.suggestFeature()
}){
HStack {
Spacer()
Text("Delete info")
.font(.system(size: 25))
.fontWeight(.medium)
.foregroundColor(.white)
.padding(.top, 15)
.padding(.bottom, 15)
Spacer()
}
}
.background(Color(red: 0.45, green: 0.19, blue: 0.20))
Button(action: {
let telephone = "tel://"
let formattedString = telephone + numberString
guard let url = URL(string: formattedString) else { return }
UIApplication.shared.open(url)
}){
HStack {
Spacer()
(Text("Call ") + Text(Image(systemName: "phone.fill")))
.font(.system(size: 25))
.fontWeight(.medium)
.foregroundColor(.white)
.padding(.top, 15)
.padding(.bottom, 15)
Spacer()
}
}
.background(Color(.black))
Spacer()
Text("© Copyright Sixish, Inc.")
.padding(.bottom, 30)
.foregroundColor(.gray)
}.sheet(item: $message) { item in
MailView(message: item)
//your code would probably need to be more like MailView(result: self.$result, newSubject: item.subject, newMsgBody: item.body)
}
}
}
}
func suggestFeature() {
print("You've got mail")
if MFMailComposeViewController.canSendMail() {
self.showSheet = true
} else {
print("Error sending mail")
// Alert : Unable to send the mail
}
}
}
struct MailView : View {
var message : Message
var body: some View {
Text(message.subject)
Text(message.body)
}
}
Note that now, your sheet is displayed if message has an item it it. Message is now a struct.
I simplified things a little bit since I didn't have your MailView container (and I temporarily commented out your result property), but the concept presented here should get you started doing what you need to do.

Related

Custom TextField Swiftui

How can I create a text field like the one shown in the figure? with background of that color and white line?
import SwiftUI
#available(iOS 16.0, *)
struct SecondView: View {
#State var nome = ""
#State var cognome = ""
var body: some View {
GeometryReader { geo in
ZStack {
Color(red: 57 / 255, green: 63 / 255, blue: 147 / 255)
Text("Ora tocca ai tuoi dati")
.font(Font.custom("Rubik", size: 22)).foregroundColor(Color.white).font(Font.body.bold()).padding(.bottom, 300).font(Font.headline.weight(.light))
Text("Per iniziare ad usare MyEntZo completa i campi qui sotto").multilineTextAlignment(.center)
.font(Font.custom("Rubik", size: 18)).foregroundColor(Color.white).font(Font.body.bold()).padding(.bottom, 80).font(Font.headline.weight(.light))
TextField("Nome", text: $nome, axis: .vertical)
.textFieldStyle(.roundedBorder)
.background(.ultraThinMaterial)
.padding().padding(.bottom, 20).padding(.top, 110).scaledToFill()
.underline()
TextField("Cognome", text: $nome, axis: .vertical)
.textFieldStyle(.roundedBorder)
.background(.ultraThinMaterial)
.padding().padding(.bottom, 20).padding(.top, 190).scaledToFill()
.underline()
Button(action: {
print("sign up bin tapped")
}){
Text("Continua")
.frame(minWidth: 0, maxWidth: .infinity)
.font(.system(size: 18))
.padding()
.foregroundColor(.white)
.overlay(
RoundedRectangle(cornerRadius: 25)
.stroke(Color.green, lineWidth: 2)
)
}
.background(Color.green)
.cornerRadius(25).frame(width: geo.size.width - 50, height: 100)
.padding(.top, 300)
}
}
.ignoresSafeArea()
}
}
struct SecondView_Previews: PreviewProvider {
static var previews: some View {
SecondView()
}
}
You can create a custom text field that wraps a regular TextField, passing in the #State as a #Binding, e.g.
struct CustomTextField: View {
let placeholder: String
#Binding var text: String
var body: some View {
VStack {
TextField(placeholder, text: $text)
.padding(.bottom, 4)
.foregroundColor(.white)
Color.white
.frame(height: 2)
}
.padding([.top, .bottom, .trailing])
.background(.blue)
}
}
which you can use like:
struct ContentView: View {
#State private var nome: String = ""
var body: some View {
VStack {
// etc
CustomTextField(placeholder: "Nome", text: $nome)
// etc
}
}
}
struct SecondView: View {
#State var nome = ""
#State var cognome = ""
var body: some View {
GeometryReader { geo in
ZStack {
Color(red: 57 / 255, green: 63 / 255, blue: 147 / 255)
VStack {
Spacer()
Text("Ora tocca ai tuoi dati")
.font(Font.custom("Rubik", size: 22))
.foregroundColor(.white)
.bold()
.padding()
.font(Font.headline.weight(.light))
Text("Per iniziare ad usare MyEntZo completa i campi qui sotto").multilineTextAlignment(.center)
.font(Font.custom("Rubik", size: 18)).foregroundColor(Color.white).font(Font.body.bold()).padding(.bottom, 80).font(Font.headline.weight(.light))
TextField("Nome", text: $nome, prompt: Text("Nome").foregroundColor(.white.opacity(0.7)))
.foregroundColor(.white)
.padding(.horizontal)
Rectangle()
.foregroundColor(.white)
.background(.white)
.frame(maxWidth: .infinity, maxHeight: 2)
.padding(.horizontal)
TextField("Cognome", text: $cognome, prompt: Text("Cognome").foregroundColor(.white.opacity(0.7)))
.foregroundColor(.white)
.padding([.horizontal, .top])
Rectangle()
.foregroundColor(.white)
.background(.white)
.frame(maxWidth: .infinity, maxHeight: 2)
.padding(.horizontal)
Button(action: {
print("sign up bin tapped")
}){
Text("Continua")
.frame(minWidth: 0, maxWidth: .infinity)
.font(.system(size: 18))
.padding()
.foregroundColor(.white)
.overlay(
RoundedRectangle(cornerRadius: 25)
.stroke(Color.green, lineWidth: 2)
)
}
.background(Color.green)
.cornerRadius(25).frame(width: geo.size.width - 50, height: 100)
.padding(.top)
Spacer()
}
}
}
.ignoresSafeArea()
}
}

Can't display custom pop-up because my tab view is in NavigationView

I want to display a pop-up in my view, where I will be able to display a menu where I can choose how I fell, then it will show what I have chosen and close itself. If I am adding it to the view and it's presenting It shows wrong and Tab Bar does not disappear. Can someone provide a better way to show a pop-up over Tab Bar? The logic is something like this: button pressed -> shows pop-up -> choose status -> shows another pop-up -> disappears.
Code's provided below:
// Smile face that used in question
struct SmileFace: View {
var text: String
var image: String
#Binding var current: String
var body: some View {
Button {
withAnimation {
current = text
}
} label: {
VStack {
Image(image)
.resizable()
.scaledToFill()
.frame(width: 46, height: 46)
Text(text)
.foregroundColor(.white)
}
}
}
}
// Check in answer view
import SwiftUI
struct CheckInAskView: View {
let didClose: () -> Void
var didChose: Bool = false
#State var current: String
var emotions = [
"GREAT" : "good",
"GOOD" : "happy",
"OK" : "moderate",
"BAD" : "sad",
"TERRIBLE" : "verysad"
]
var body: some View {
VStack {
ZStack(alignment: .topLeading) {
Rectangle()
.fill(Color("navigation"))
.cornerRadius(15)
.frame(height: 601)
HStack(alignment: .top) {
ZStack(alignment: .top) {
Circle()
.fill(Color(red: 0.682, green: 0.384, blue: 0.486).opacity(0.10))
.frame(width: 173)
.offset(x: -173/2, y: -90/2)
.clipped()
}
Spacer()
Button {
didClose()
} label: {
ZStack {
Circle()
.fill(Color(red: 0.933, green: 0.933, blue: 0.933).opacity(0.30))
.frame(width: 24)
.clipped()
Image(systemName: "xmark")
.font(.system(size: 15))
.foregroundColor(Color(red: 0.762, green: 0.762, blue: 0.762))
}
.padding(10)
}
}
VStack(alignment: .center) {
Spacer()
Text("How do you feel now?")
.foregroundColor(.white)
.font(.custom("Manrope-Bold", size: 16))
HStack(spacing: 35) {
SmileFace(text: "GOOD", image: "good", current: $current)
}
.padding(.horizontal)
DoubleTextView(topText: "Recommendation for you", buttomText: "We have selected courses based on your goals and \nexperience", topTextSize: 16, buttomTextSize: 14)
BigFrameScrollViewHorizontal()
Spacer()
}
}
.frame(height: 601)
}
.frame(height: 601)
.transition(.move(edge: .bottom))
}
}
// View with all views
struct CheckInView: View {
#StateObject var sheetManager: SheetManager
var body: some View {
VStack {
HStack {
Text("How do you fell now?")
.foregroundColor(.white)
.font(.custom("Manrope-Bold", size: 16))
Spacer()
Button {
} label: {
ZStack {
HStack {
Text("Pass check in")
.padding([.top, .leading, .bottom])
.foregroundColor(.white)
.font(.custom("Manrope-Medium", size: 12))
Image(systemName: "chevron.right")
.foregroundColor(.white)
.font(.system(size: 15))
.padding(.trailing)
}
}
.background(Rectangle()
.fill(Color("active"))
.cornerRadius(100)
.frame(height: 26))
}
}
VStack {
Divider()
.background(Color("inactive"))
.padding(.vertical)
Divider()
.background(Color("inactive"))
.padding(.vertical)
Divider()
.background(Color("inactive"))
.padding(.vertical)
Divider()
.background(Color("inactive"))
.padding(.vertical)
Divider()
.background(Color("inactive"))
.padding(.vertical)
Divider()
.background(Color("inactive"))
.padding(.vertical)
}
HStack {
Button {
} label: {
ZStack {
Circle()
.fill(Color("navigation"))
.frame(width: 26)
Image(systemName: "chevron.left")
.foregroundColor(.white)
.font(.system(size: 14))
}
}
Button {
} label: {
ZStack {
Circle()
.fill(Color("navigation"))
.frame(width: 26)
Image(systemName: "chevron.right")
.foregroundColor(Color("inactive"))
.font(.system(size: 14))
}
}
}
.padding(.top)
}
.padding(.horizontal, 15.0)
}
}
struct CheckInView_Previews: PreviewProvider {
static var previews: some View {
CheckInView(sheetManager: SheetManager())
.background(.yellow)
}
}
If I have all of this, how can I create a pop-up over Tab View? Maybe a link to a similar problem?

Can’t get the sum of ForEach items

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
}
}
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)

SwiftUI button glitches on keyboard dismiss

I have a button on my view that, when pressed, calls a hideKeyboard function which is the following:
func hideKeyboard() {
let resign = #selector(UIResponder.resignFirstResponder)
UIApplication.shared.sendAction(resign, to: nil, from: nil, for: nil)
}
However, when the button is pressed and the keyboard gets dismissed, a glitch occurs where the button stays in place while the view moves downward:
https://giphy.com/gifs/Z7qCDpRSGoOb9CbVRQ
Reproducible example:
import SwiftUI
struct TestView: View {
#State private var username: String = ""
#State private var password: String = ""
#State private var isAnimating: Bool = false
var lightGrey = Color(red: 239.0/255.0,
green: 243.0/255.0,
blue: 244.0/255.0)
var body: some View {
ZStack() {
VStack() {
Spacer()
Text("Text")
.font(.title)
.fontWeight(.semibold)
.padding(.bottom, 15)
.frame(maxWidth: .infinity, alignment: .leading)
Text("Text")
.font(.subheadline)
.padding(.bottom)
.frame(maxWidth: .infinity, alignment: .leading)
TextField("username", text: $username)
.padding()
.background(self.lightGrey)
.cornerRadius(5.0)
.padding(.bottom, 20)
SecureField("password", text: $password)
.padding()
.background(self.lightGrey)
.cornerRadius(5.0)
.padding(.bottom, 20)
Button(action: {
self.hideKeyboard()
login()
})
{
if isAnimating {
ProgressView()
.colorScheme(.dark)
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(maxWidth: .infinity)
.background(Color.green)
.cornerRadius(10.0)
.padding(.bottom, 20)
}
else {
Text("Text")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(maxWidth: .infinity)
.background(Color.green)
.cornerRadius(10.0)
.padding(.bottom, 20)
}
}
.disabled(username.isEmpty || password.isEmpty || isAnimating)
Text("Text")
.font(.footnote)
.frame(maxWidth: .infinity, alignment:.leading)
Spacer()
}
.padding()
.padding(.bottom, 150)
.background(Color.white)
}
}
func hideKeyboard() {
let resign = #selector(UIResponder.resignFirstResponder)
UIApplication.shared.sendAction(resign, to: nil, from: nil, for: nil)
}
func login() {
isAnimating = true
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
This is due to view replacement inside button, find below a fixed re-layout.
Tested with Xcode 13.2 / iOS 15.2 (slow animation was activated for demo)
Button(action: {
self.hideKeyboard()
login()
})
{
VStack { // << persistent container !!
if isAnimating {
ProgressView()
.colorScheme(.dark)
}
else {
Text("Text")
}
}
.foregroundColor(.white)
.font(.headline)
.padding()
.frame(maxWidth: .infinity)
.background(Color.green)
.cornerRadius(10.0)
.padding(.bottom, 20)
}
.disabled(username.isEmpty || password.isEmpty || isAnimating)
Looks like a conflict between UIApplication.shared.sendAction and SwiftUI: hiding the keyboard starts the animation, but updating the isAnimating property resets it.
Changing the order of calls solves the problem:
Button(action: {
login()
self.hideKeyboard()
})

The leading alignment doesn't apply to the text SwiftUI

I have the following
struct hScrollView: View {
#State private var meals = ["short", "long Text", "really long text"]
var body: some View {
VStack(alignment: .leading) {
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 30) {
ForEach(0 ..< meals.count) { count in
VStack(alignment: .leading) {
HStack(alignment: .center) { // .center is by default, so also remove
Text("\(count + 1) ")
.foregroundColor(Color.black)
.multilineTextAlignment(.leading)
.padding(.horizontal, 15)
.padding(.vertical, 5)
.background(
Capsule()
.fill(Color.black)
.opacity(0.20)
)
Spacer()
Text("\(self.meals[count]) ")
.foregroundColor(Color.black)
.multilineTextAlignment(.leading)
Spacer()
Button(action: {
print("Button was tapped")
}) {
Image(systemName: "pencil")
.foregroundColor(Color.yellow)
.font(.system(size: 25, weight: .bold))
.shadow(radius: 1)
}
}
}
.padding(.horizontal)
}
}
.frame(width: UIScreen.main.bounds.width) // set a fixed width
}
.frame(height: 500)
.frame(maxWidth: 400)
.padding(.top, 190)
}
}
}
If you noticed, the text is centered, I want it that way, but all beginning with .leading alignment so they all start at the same start point. What can I fix there? I tried putting the leading alignment in all the stacks :/
Thanks
Your view would be like
(1)|-------|short|-------|✏️
(2)|-----|long text|-----|✏️
(3)|-|really long text|-|✏️
so you should remove the Spacer() between the first Text and second instead of set text alignment.
HStack(alignment: .center) { // .center is by default, so also remove
Text("\(count + 1) ")
.foregroundColor(Color.black)
.multilineTextAlignment(.leading)
.padding(.horizontal, 15)
.padding(.vertical, 5)
.background(
Capsule()
.fill(Color.black)
.opacity(0.20)
)
// Spacer()
Text("\(self.meals[count]) ")
.foregroundColor(Color.black)
.multilineTextAlignment(.leading)
Spacer()
Button(action: {
print("Button was tapped")
}) {
Image(systemName: "pencil")
.foregroundColor(Color.yellow)
.font(.system(size: 25, weight: .bold))
.shadow(radius: 1)
}
}