Why is my SwiftUI view not updating when the model changes - swift
How do I update the content of an existing row in a List?
The print statement shows that the button is updating the Bool, but the View does not update .
the content (button) moves as expected, but the action and formating does not change as expected.
the code for the page I am using:
struct NotificationView: View {
#ObservedObject var notificationVModel: NotificationVModel = NotificationVModel()
var body: some View {
NavigationView{
List(notificationVModel.notificationarray,id:\.NotificationName){notificationVV in
ZStack {
if notificationVV.isShgowen {
Color (.green).opacity(0.1)
.cornerRadius(10)
}
HStack{
Button(action: {
notificationVV.changTogle()
print("\(notificationVV.isShgowen)")
}, label: {
ZStack{
Circle()
.foregroundColor(Color(red: 0 / 255, green: 175 / 255, blue: 80 / 255))
.frame(width: 50, height: 50, alignment: .center)
Image(systemName: "bell")
.frame(width: 40, height: 40, alignment: .center)
.foregroundColor(.white)
} })
VStack{
HStack {
if notificationVV.isShgowen{
Text("true")
.font(.custom("AvenirNext-DemiBold", size: 10))
}
Text(notificationVV.NotificationName)
.font(.custom("AvenirNext-DemiBold", size: 20))
Spacer()
Text(notificationVV.NotifivationDate)
.font(.custom("AvenirNext-Medium", size: 12))
.foregroundColor(.gray)
}.padding(.leading).padding(.trailing)
Text(notificationVV.NotificationDiscrip)
.font(.custom("AvenirNext-Regular", size: 11))
.lineLimit(nil)
.padding(.leading).padding(.trailing)
}
}.padding()
}
}
.navigationTitle("Notification")
.navigationBarTitleDisplayMode(.inline)
}
}
}
The ViewModel
class NotificationVModel: ObservableObject {
#Published var notificationarray : [NotificationV] = [
NotificationV(NotificationName: "Notification 1", NotificationDiscrip: "Lorem ipsum dolor sit amet,consec tetur adipiscing elit Lorem ipsum doloramet,consec tetur adipiscing elit sit ipi piscing… ", NotifivationDate: "04/02/2021", isShgowen: false), NotificationV(NotificationName: "Notification 2", NotificationDiscrip: "Lorem ipsum dolor sit amet,consec tetur adipiscing elit Lorem ipsum doloramet,consec tetur adipiscing elit sit ipi piscing… ", NotifivationDate: "05/03/2021", isShgowen: true),
]
}
The model
class NotificationV : ObservableObject{
let objectWillChange = ObservableObjectPublisher()
#Published var NotificationName : String = ""
#Published var NotificationDiscrip: String = ""
#Published var NotifivationDate:String = ""
#Published var isShgowen:Bool = false
init(NotificationName: String, NotificationDiscrip: String, NotifivationDate: String, isShgowen: Bool) {
self.NotificationName = NotificationName
self.NotificationDiscrip = NotificationDiscrip
self.NotifivationDate = NotifivationDate
self.isShgowen = isShgowen
}
func changTogle(){
if isShgowen == false {
isShgowen = true
}
}
}
Use struct rather than class for the NotificationV.
Create a new view for the buttons and send both your viewmodel and model to the new view.
Find index of model
Fore more info, read this tutorial
https://developer.apple.com/tutorials/swiftui/building-lists-and-navigation
struct NotificationView: View {
#ObservedObject var notificationVModel: NotificationVModel = NotificationVModel()
var body: some View {
NavigationView{
List(notificationVModel.notificationarray,id:\.NotificationName){notificationVV in
ZStack {
if notificationVV.isShowen {
Color (.green).opacity(0.1)
.cornerRadius(10)
}
HStack{
ToggleNotification(notifications: $notificationVModel.notificationarray, notificationV: notificationVV)
VStack{
HStack {
Text(notificationVV.isShowen ? "true": "false")
if notificationVV.isShowen {
Text("true")
.font(.custom("AvenirNext-DemiBold", size: 10))
}
Text(notificationVV.NotificationName)
.font(.custom("AvenirNext-DemiBold", size: 20))
Spacer()
Text(notificationVV.NotifivationDate)
.font(.custom("AvenirNext-Medium", size: 12))
.foregroundColor(.gray)
}.padding(.leading).padding(.trailing)
Text(notificationVV.NotificationDiscrip)
.font(.custom("AvenirNext-Regular", size: 11))
.lineLimit(nil)
.padding(.leading).padding(.trailing)
}
}.padding()
}
}
.navigationTitle("Notification")
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct ToggleNotification:View {
#Binding var notifications: [NotificationV]
var notificationV: NotificationV
var index:Int? {
notifications.firstIndex { $0.NotificationName == notificationV.NotificationName}
}
var body: some View {
Button(action: {
notifications[index!].isShowen.toggle()
print("\($notifications[index!].isShowen)")
}, label: {
ZStack{
Circle()
.foregroundColor(Color(red: 0 / 255, green: 175 / 255, blue: 80 / 255))
.frame(width: 50, height: 50, alignment: .center)
Image(systemName: "bell")
.frame(width: 40, height: 40, alignment: .center)
.foregroundColor(.white)
}
})
}
}
struct NotificationV {
var NotificationName : String = ""
var NotificationDiscrip: String = ""
var NotifivationDate:String = ""
var isShowen:Bool = false
init(NotificationName: String, NotificationDiscrip: String, NotifivationDate: String, isShowen: Bool) {
self.NotificationName = NotificationName
self.NotificationDiscrip = NotificationDiscrip
self.NotifivationDate = NotifivationDate
self.isShowen = isShowen
}
}
class NotificationVModel: ObservableObject {
#Published var notificationarray : [NotificationV] = [
NotificationV(NotificationName: "Notification 1", NotificationDiscrip: "Lorem ipsum dolor sit amet,consec tetur adipiscing elit Lorem ipsum doloramet,consec tetur adipiscing elit sit ipi piscing… ", NotifivationDate: "04/02/2021", isShowen: false), NotificationV(NotificationName: "Notification 2", NotificationDiscrip: "Lorem ipsum dolor sit amet,consec tetur adipiscing elit Lorem ipsum doloramet,consec tetur adipiscing elit sit ipi piscing… ", NotifivationDate: "05/03/2021", isShowen: true),
]
}
Follow case conventions. Names of types and protocols are UpperCamelCase. Everything else is lowerCamelCase.
https://swift.org/documentation/api-design-guidelines/
Update
As of now, NotificationV must unique NotificationName. Regardless of that, NotificationV should confirms to both Identifiable, Equatable protocols and use the following in ToggleNotification
var index:Int? {
notifications.firstIndex { $0 == notificationV}
}
NotificationV
struct NotificationV: Identifiable, Equatable {
var id: UUID = UUID()
var NotificationName : String = ""
var NotificationDiscrip: String = ""
var NotifivationDate:String = ""
var isShowen:Bool = false
init(NotificationName: String, NotificationDiscrip: String, NotifivationDate: String, isShowen: Bool) {
self.NotificationName = NotificationName
self.NotificationDiscrip = NotificationDiscrip
self.NotifivationDate = NotifivationDate
self.isShowen = isShowen
}
}
Doing so you always find correct NotificationV even if they do not have unique name.
Related
SwiftUI: onChange with #Binding from outside view
Updated to provide full reproducible example. I have a view MainView with two sub-views, MainTextView and TableOfContentsView. I want to select an item in TableOfContentsView which triggers a scroll position change in MainTextView. I have an #State var scrollPosition: Int in MainView which is passed to an #Binding var scrollPosition: Int in MainTextView. Here is the code for my 3 views. struct MainView: View { #State private var showingToc = false #State private var scrollPosition = 0 var body: some View { VStack(alignment: .leading) { Text("Table of Contents (chapter \(scrollPosition + 1))") .onTapGesture { showingToc = !showingToc } Divider() if showingToc { TableOfContentsView( onChapterSelected: { chapter in scrollPosition = chapter showingToc = false } ) } else { MainTextView(scrollPosition: $scrollPosition) } } } } struct TableOfContentsView: View { var onChapterSelected: (Int) -> Void var body: some View { ScrollView { VStack { ForEach(0 ..< 20) { i in Text("Chapter \(i + 1)") .onTapGesture { onChapterSelected(i) } } } } } } struct MainTextView: View { let lorumIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." #Binding var scrollPosition: Int #State private var scrollProxy: ScrollViewProxy? = nil var body: some View { ScrollView { ScrollViewReader { proxy in VStack(alignment: .leading) { ForEach(0 ..< 20) { i in VStack(alignment: .leading) { Text("Chapter \(i + 1)") .frame(maxWidth: .infinity, alignment: .center) .padding(.top) Text(lorumIpsum) .font(.system(size: 18, design: .serif)) .padding(.top) } .padding(.bottom, 20) .id(i) } } .padding(.leading) .padding(.trailing) .onAppear { scrollProxy = proxy } } } .onChange(of: scrollPosition) { target in scrollProxy?.scrollTo(target, anchor: .top) } } } My .onChange function is never called - if I place a breakpoint inside, it never hits the breakpoint. What am I missing here? How do I observe an #Binding that is changed from outside of the view?
.onChange is not going to get called cause MainTextView doesn't exist when showingToc = true because of the conditional block: if showingToc { TableOfContentsView(onChapterSelected: { chapter in scrollPosition = chapter showingToc = false } ) } else { MainTextView(scrollPosition: $scrollPosition) } You might want to consider showing TOC as an overlay. But to get your current code working, you need to call proxy.scrollTo in your .onAppear of MainTextView: .onAppear { scrollProxy = proxy scrollProxy?.scrollTo(scrollPosition, anchor: .top) }
SwiftUI: How to change the TextField height dynamicly to a threshold and then allow scrolling?
The LineLimit does not work: TextField("Text", text: $model.commitDescr) .multilineTextAlignment(.leading) .lineLimit(5) LineLimit change nothing - I'm able to display 10-20-30 lines. Similar way with scrollView allways have maxHeight(400) insteaad of dymamic height: ScrollView() { TextField("Text", text: $model.commitDescr) .multilineTextAlignment(.leading) .lineLimit(5) } .frame(minHeight: 20, maxHeight: 400) The following also does not work properly: TextField("Text", text: $model.commitDescr) .multilineTextAlignment(.leading) .lineLimit(5) .frame(minHeight: 20, maxHeight: 400)
Here is a way for this issue: struct ContentView: View { #State private var commitDescr: String = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." var body: some View { TextEditorView(string: $commitDescr) } } struct TextEditorView: View { #Binding var string: String #State private var textEditorHeight : CGFloat = CGFloat() var body: some View { ZStack(alignment: .leading) { Text(string) .lineLimit(5) .foregroundColor(.clear) .padding(.top, 5.0) .padding(.bottom, 7.0) .background(GeometryReader { Color.clear.preference(key: ViewHeightKey.self, value: $0.frame(in: .local).size.height) }) TextEditor(text: $string) .frame(height: textEditorHeight) } .onPreferenceChange(ViewHeightKey.self) { textEditorHeight = $0 } } } struct ViewHeightKey: PreferenceKey { static var defaultValue: CGFloat { 0 } static func reduce(value: inout Value, nextValue: () -> Value) { value = value + nextValue() } }
#available(iOS 14.0, macOS 11.0, *) use TextEditor instead of TextField TextEditor(text: $text) .fixedSize(horizontal: false, vertical: true) .multilineTextAlignment(.leading) in use example : ScrollView { VStack{ TextEditor(text: $text) .frame(minHeight: 40, alignment: .leading) .frame(maxHeight: MAX_HEIGHT) .cornerRadius(10, antialiased: true) .foregroundColor(.black) .font(.body) .padding() .fixedSize(horizontal: false, vertical: true) .multilineTextAlignment(.leading) } .background(Color.red) }
Fixed height, allows scrolling Use TextEditor, rather than TextField. You then set the maxHeight to whatever you would like. struct ContentView: View { #State private var text = "" var body: some View { TextEditor(text: $text) .frame(maxHeight: 300) } } Result: Fixed number of lines, no scrolling If you want to limit the number of lines you can display with TextEditor, you can use SwiftUI-Introspect to do that. Example: struct ContentView: View { #State private var text = "" var body: some View { TextEditor(text: $text) .introspectTextView { textView in textView.textContainer.maximumNumberOfLines = 5 textView.textContainer.lineBreakMode = .byTruncatingTail } } } Result:
Usually, a TextField is only for one line of text. For multiple lines of input, a TextEditor may be a better solution in native SwiftUI. You could also set a .frame(maxHeight: XX, alignment: .leading) for this. To be clear, the example beneath will make sure the editor is always 200 points high, even when no text is entered: TextEditor(text: $variable) .frame(maxHeight: 200, alignment: .center) On request of the asker, an extra example where the editor is small (50 points high) and only expands to the maxHeight when text is entered: TextEditor(text: $variable) .frame(minHeight: 50, maxHeight: 200, alignment: .center) Proof of concept: https://imgur.com/a/kmu1gIe
SwiftUI Problem GeometryReader with Zstack
I'm trying to create my own PhoneTextField in and I have found a tutorial in here it's working fine if don't call it inside ZStack let's see the code example as below countryCode View struct CountryCodes: View { #Binding var countryCode : String #Binding var countryFlag : String #Binding var y : CGFloat var body: some View { GeometryReader { geo in List(self.countryDictionary.sorted(by: <), id: \.key) { key , value in HStack { Text("\(self.flag(country: key))") Text("\(self.countryName(countryCode: key) ?? key)") Spacer() Text("+\(value)").foregroundColor(.secondary) }.background(Color.white) .font(.system(size: 20)) .onTapGesture { self.countryCode = value self.countryFlag = self.flag(country: key) withAnimation(.spring()) { self.y = 350 } } } .padding(.bottom) .frame(width: geo.size.width, height: 500) .position(x: geo.frame(in: .global).midX, y: geo.frame(in: .global).maxY - -200) } } let countryDictionary = ["AF":"93","AL":"355","DZ":"213","US":"1", "AD":"376","AO":"244","AI":"1","AG":"1","AR":"54", "AM":"374","AW":"297","AU":"61","AT":"43","AZ":"994", "BS":"1","BH":"973","BD":"880","BB":"1","BY":"375", "BE":"32","BZ":"501","BJ":"229","BM":"1","BT":"975", "BA":"387","BW":"267","BR":"55","IO":"246","BG":"359", "BF":"226","BI":"257","KH":"855","CM":"237","CA":"1", "CV":"238","KY":"345","CF":"236","TD":"235","CL":"56","CN":"86", "CX":"61","CO":"57","KM":"269","CG":"242","CK":"682","CR":"506", "HR":"385","CU":"53","CY":"537","CZ":"420","DK":"45","DJ":"253", "DM":"1","DO":"1","EC":"593","EG":"20","SV":"503","GQ":"240", "ER":"291","EE":"372","ET":"251","FO":"298","FJ":"679","FI":"358", "FR":"33","GF":"594","PF":"689","GA":"241","GM":"220","GE":"995", "DE":"49","GH":"233","GI":"350","GR":"30","GL":"299","GD":"1", "GP":"590","GU":"1","GT":"502","GN":"224","GW":"245","GY":"595","HT":"509", "HN":"504","HU":"36","IS":"354","IN":"91","ID":"62","IQ":"964", "IE":"353","IL":"972","IT":"39","JM":"1","JP":"81","JO":"962", "KZ":"77","KE":"254","KI":"686","KW":"965","KG":"996","LV":"371", "LB":"961","LS":"266","LR":"231","LI":"423","LT":"370","LU":"352", "MG":"261","MW":"265","MY":"60","MV":"960","ML":"223", "MT":"356","MH":"692","MQ":"596","MR":"222","MU":"230","YT":"262", "MX":"52","MC":"377","MN":"976", "ME":"382","MS":"1","MA":"212", "MM":"95","NA":"264","NR":"674","NP":"977","NL":"31","NC":"687", "NZ":"64","NI":"505","NE":"227","NG":"234","NU":"683", "NF":"672","MP":"1","NO":"47","OM":"968","PK":"92","PW":"680", "PA":"507","PG":"675","PY":"595","PE":"51","PH":"63","PL":"48", "PT":"351","PR":"1","QA":"974","RO":"40","RW":"250","WS":"685", "SM":"378","SA":"966","SN":"221","RS":"381","SC":"248", "SL":"232","SG":"65","SK":"421","SI":"386","SB":"677", "ZA":"27","GS":"500","ES":"34","LK":"94","SD":"249","SR":"597", "SZ":"268","SE":"46","CH":"41","TJ":"992","TH":"66","TG":"228", "TK":"690","TO":"676","TT":"1","TN":"216","TR":"90", "TM":"993","TC":"1","TV":"688","UG":"256","UA":"380", "AE":"971","GB":"44","AS":"1","UY":"598","UZ":"998", "VU":"678","WF":"681","YE":"967","ZM":"260", "ZW":"263","BO":"591","BN":"673","CC":"61", "CD":"243","CI":"225","FK":"500","GG":"44", "VA":"379","HK":"852","IR":"98","IM":"44", "JE":"44","KP":"850","KR":"82","LA":"856", "LY":"218","MO":"853","MK":"389","FM":"691", "MD":"373","MZ":"258","PS":"970","PN":"872", "RE":"262","RU":"7","BL":"590","SH":"290","KN":"1", "LC":"1","MF":"590","PM":"508","VC":"1","ST":"239", "SO":"252","SJ":"47","SY":"963","TW":"886","TZ":"255", "TL":"670","VE":"58","VN":"84","VG":"284","VI":"340"] func countryName(countryCode: String) -> String? { let current = Locale(identifier: "en_US") return current.localizedString(forRegionCode: countryCode) } func flag(country:String) -> String { let base : UInt32 = 127397 var flag = "" for v in country.unicodeScalars { flag.unicodeScalars.append(UnicodeScalar(base + v.value)!) } return flag } } RegistrationView // // RegisterView.swift // Instagram // // Created by Admin on 4/4/21. // import SwiftUI import Combine struct RegistrationView: View { #State var isEditing: Bool = false // navigate to verification view #State private var selection: String? = nil #State private var email:String = "" #State private var phone:String = "" #State private var fullname = "" #State private var username = "" #State private var password = "" #State private var selectedImage:UIImage? #State private var image:Image? #Environment(\.presentationMode) var mode #State var imagePickerPresented = false #EnvironmentObject var viewModel: AuthViewModel #State var isVerification = false #State var phoneNumber = "" #State var y : CGFloat = 350 #State var countryCode = "" #State var countryFlag = "" #State var isInputPhoneNumber = true #State var isInputEmail = false #State var tempData = DataTempo() #State var isShowSignInButton = true // var successClosure: (Bool) -> Void { // // } #EnvironmentObject var mobileVerificationViewModel: MobileVerificationNvm var body: some View { ZStack { LinearGradient(gradient: Gradient(colors: [Color.purple, Color("PrimaryColor")]), startPoint: .top, endPoint: .bottom) .ignoresSafeArea() VStack{ // Spacer() VStack(spacing:20){ VStack(alignment:.leading){ Text("Sign Up") .font(.largeTitle) .bold() .foregroundColor(.white) }.padding(.horizontal,32).frame(width: UIScreen.main.bounds.width, alignment: .leading) Toggle( isInputEmail ? "Use Email" : "Use Phone Number", isOn: $isInputEmail) .foregroundColor(.white) .toggleStyle(SwitchToggleStyle(tint: Color(#colorLiteral(red: 0.5568627715, green: 0.3529411852, blue: 0.9686274529, alpha: 1)))) .padding(.horizontal,32) if isInputEmail == true { CustomTextField(text: $email, placeholder: Text("Email"), imageName: "envelope") .padding() .background(Color(.init(white: 1, alpha: 0.15))) .cornerRadius(10) .foregroundColor(.white) .padding(.horizontal,32) .onChange(of: email) { // self.autocomplete($0) // like this if viewModel.isValidEmail(testStr: $0){ print("email") //phone number } } } if isInputEmail == false{ ZStack(alignment:.center) { HStack (spacing: 0) { Text(countryCode.isEmpty ? "🇹🇭 +66" : "\(countryFlag) +\(countryCode)") .frame(width: 100, height: 50) // .background(Color.secondary.opacity(0.2)) .background(Color(.init(white: 1, alpha: 0.15))) .cornerRadius(10) // .foregroundColor(countryCode.isEmpty ? .secondary : .black) .foregroundColor(.white) .onTapGesture { isShowSignInButton = false withAnimation (.spring()) { self.y = 0 } } TextField("Phone Number", text: $phone) .onChange(of: phone) { newValue in if !phone.isEmpty { self.isShowSignInButton = true if viewModel.validatePhoneNumber(value:newValue){ print("phone") //phone number self.isInputPhoneNumber = true self.isInputEmail = false } } else{ self.isShowSignInButton = false } } .frame(width: 250, height: 50) .keyboardType(.phonePad) .background(Color(.init(white: 1, alpha: 0.15))) .foregroundColor(.white) } CountryCodes(countryCode: $countryCode, countryFlag: $countryFlag, y: $y) .offset(y: y) RoundedRectangle(cornerRadius: 10).stroke() .frame(width: 350, height: 50) .foregroundColor(.white) } .zIndex(1).frame( height: 50, alignment: .center) } CustomTextField(text: $username, placeholder: Text("Username"), imageName: "person") .padding() .background(Color(.init(white: 1, alpha: 0.15))) .cornerRadius(10) .foregroundColor(.white) .padding(.horizontal,32) CustomTextField(text: $fullname, placeholder: Text("Full Name"), imageName: "person") .padding() .background(Color(.init(white: 1, alpha: 0.15))) .cornerRadius(10) .foregroundColor(.white) .padding(.horizontal,32) CustomSecureField(text: $password, placeholder: Text("Password")) .padding() .background(Color(.init(white: 1, alpha: 0.15))) .cornerRadius(10) .foregroundColor(.white) .padding(.horizontal,32) } .alert(isPresented: $isInputPhoneNumber, content: { Alert(title: Text("Message"), message: Text("you're using phone number to register"), dismissButton: .default(Text("Bye alert!"), action: { isInputPhoneNumber = true })) }) .alert(isPresented: mobileVerificationViewModel.alertBinding) { Alert(title: Text(mobileVerificationViewModel.errorMessage ?? "(unknown)"), message: nil, dismissButton: .cancel()) } HStack{ Spacer() Button(action: /*#START_MENU_TOKEN#*/{}/*#END_MENU_TOKEN#*/, label: { Text("Forgot Password") }) .font(.system(size: 13, weight: .semibold)) .foregroundColor(.white) .padding(.top) .padding(.trailing,28) } ZStack{ NavigationLink(destination:VerificationView { (pin, successClosure) in print(pin) mobileVerificationViewModel.codeVerification(phoneNumber: isInputEmail ? tempData.email : tempData.phone, pinCode: pin, email: email, password: password, fullname: fullname, username: username) //navigation back self.mode.wrappedValue.dismiss() // print(successClosure) }, tag: "verification", selection: $selection) { EmptyView() } Button(action: { self.selection = "verification" var phoneWithCountryCode = "" // if countryCode == "" { // phoneWithCountryCode = "+66\(phone)" // } // if countryCode != ""{ // phoneWithCountryCode = "+\(countryCode)\(phone)" // } // // // "+\(countryCode == "" ? "66" : countryCode + phone)" // if (!email.isEmpty || !phone.isEmpty) && !password.isEmpty{ // // if isInputEmail { // MobileVerificationNvm.shared.sendVerification(phoneNumber:email) // isVerification = true // tempData.email = email // } // // else{ // DispatchQueue.main.async { // // print("phone:\(phoneWithCountryCode)") // MobileVerificationNvm.shared.sendVerification(phoneNumber: phoneWithCountryCode) // isVerification = true // tempData.phone = phone // } // // } // // } // viewModel.register(withEmail: email, password: password,image: selectedImage,fullname: fullname,username: username) }, label: { Text("Sign Up") }).zIndex(0) // Spacer() } .font(.headline) .frame(width: 360, height: 50) .foregroundColor(.white) .background(Color(#colorLiteral(red: 0.5568627715, green: 0.3529411852, blue: 0.9686274529, alpha: 1))) // .clipShape(Capsule()) .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) .padding() .alert(isPresented: $isInputEmail, content: { Alert(title: Text("Message"), message: Text("Now! you're using Email to register but you also register with email just swtich on togle button"), dismissButton: .default(Text("Bye alert!"), action: { isInputEmail = true })) }) Button(action: { mode.wrappedValue.dismiss() // isVerification = true }, label: { Text("Already have an account? ") .font(.system(size: 14)) + Text("Sign In") .font(.system(size: 14, weight: .bold)) }) .foregroundColor(.white) .padding(.bottom,32) } } .ignoresSafeArea() } } extension RegistrationView{ func loadImage() { guard let selectedImage = selectedImage else { return } image = Image(uiImage: selectedImage) } } // struct RegistrationView_Previews: PreviewProvider { static var previews: some View { RegistrationView() } } this is my issue when I tap on country code to choose the country code you will see the button sign in over countries code list you also can check from [here]:github.com/PhanithNoch/SwiftUIPhoneTextField this is the repo that I tried to separate code from my real project as an example I stuck with this a few days ago hope anyone can help, please.
Animate view to fullscreen (Card to detail)
Im trying to replicate the cards from the Appstore's today tab. This is what I have so far: struct ContentView: View { #State var showDetail = false #State var selectedForDetail : Post? var posts = [...] // Just sample data: Post(subtitle: "test1", title: "title1", extra: "Lorem ipsum dolor...") etc. var body: some View { ZStack { ScrollView{ ForEach(self.posts){ current in PostView(post: current, isDetailed: self.$showDetail).onTapGesture { self.selectedForDetail = current withAnimation(.spring()){ self.showDetail.toggle() } } } } if showDetail { PostView(post: selectedForDetail!, isDetailed: self.$showDetail).onTapGesture { withAnimation(.spring()){ self.showDetail.toggle() } } } } } } struct PostView : View { var post : Post #Binding var isDetailed : Bool var body : some View { VStack(alignment: .leading){ HStack(alignment: .top){ Text(post.subtitle) Spacer() }.padding([.top, .horizontal]) Text(post.title).padding([.horizontal, .bottom]) if isDetailed { Text(post.extra).padding([.horizontal, .bottom]) Spacer() } } .background(isDetailed ? Color.green : Color.white) .cornerRadius(isDetailed ? 0 : 16) .shadow(radius: isDetailed ? 0 : 12) .padding(isDetailed ? [] : [.top, .horizontal]) .edgesIgnoringSafeArea(.all) } } struct Post : Identifiable { var id = UUID() var subtitle : String var title : String var extra : String } It works so far that pressing a PostView shows a detailed PostView in fullscreen. But the animation looks way off. I also tried to follow these video tutorials: https://www.youtube.com/watch?v=wOQWAzsKi4U https://www.youtube.com/watch?v=8gDtf22TwW0 But these only worked with static content (and no ScrollView. Using one results in overlapped PostViews) or didn't look right.. So my question is how can i improve my code to get as close as possible to the todays tab in die Appstore? Is my approach even feasible? Thanks in advance
Please find below your code modified to fit your needs import SwiftUI struct ContentView: View { #State var selectedForDetail : Post? #State var showDetails: Bool = false // Posts need to be #State so changes can be observed #State var posts = [ Post(subtitle: "test1", title: "title1", extra: "Lorem ipsum dolor..."), Post(subtitle: "test1", title: "title1", extra: "Lorem ipsum dolor..."), Post(subtitle: "test1", title: "title1", extra: "Lorem ipsum dolor..."), Post(subtitle: "test1", title: "title1", extra: "Lorem ipsum dolor..."), Post(subtitle: "test1", title: "title1", extra: "Lorem ipsum dolor...") ] var body: some View { ScrollView { VStack { ForEach(self.posts.indices) { index in GeometryReader { reader in PostView(post: self.$posts[index], isDetailed: self.$showDetails) .offset(y: self.posts[index].showDetails ? -reader.frame(in: .global).minY : 0) .onTapGesture { if !self.posts[index].showDetails { self.posts[index].showDetails.toggle() self.showDetails.toggle() } } // Change this animation to what you please, or change the numbers around. It's just a preference. .animation(.spring(response: 0.6, dampingFraction: 0.6, blendDuration: 0)) // If there is one view expanded then hide all other views that are not .opacity(self.showDetails ? (self.posts[index].showDetails ? 1 : 0) : 1) } .frame(height: self.posts[index].showDetails ? UIScreen.main.bounds.height : 100, alignment: .center) .simultaneousGesture( // 500 will disable ScrollView effect DragGesture(minimumDistance: self.posts[index].showDetails ? 0 : 500) ) } } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } struct PostView : View { #Binding var post : Post #Binding var isDetailed : Bool var body : some View { VStack(alignment: .leading){ HStack(alignment: .top){ Text(post.subtitle) Spacer() // Only show close button if page is showing in full screen if(self.isDetailed) { // Close Button Button(action: { self.post.showDetails.toggle() self.isDetailed.toggle() }) { Text("X") .frame(width: 48, height: 48, alignment: .center) .background(Color.white) .clipShape(Circle()) }.buttonStyle(PlainButtonStyle()) } }.padding([.top, .horizontal]) Text(post.title).padding([.horizontal, .bottom]) if isDetailed { Text(post.extra).padding([.horizontal, .bottom]) Spacer() } } .background(isDetailed ? Color.green : Color.white) .cornerRadius(isDetailed ? 0 : 16) .shadow(radius: isDetailed ? 0 : 12) .padding(isDetailed ? [] : [.top, .horizontal]) .edgesIgnoringSafeArea(.all) } } struct Post : Identifiable { var id = UUID() var subtitle : String var title : String var extra : String var showDetails: Bool = false // We need this variable to control each cell individually } If any explanation is needed please let me know. Note: I added a showDetails property to your Post model, this is needed to control individual cells. Keep in mind best practice is to separate that into a different array to take care of whats visible and what not but this will do for now. Also note we are looping through indices of our array and not the objects, this way we have flexibility of choosing what to show.
Dynamically Resizing only one dimension of an element
So I am implementing a UI in SwiftUI and having trouble implementing the little "title tab" all the way to the left in the picture below. Basically I have a title that is rotated 90 degrees to display on the side of the tab and I want the user to be able to enter a custom title so I need the title area to be able to dynamically resize. However I also have it embeded in an HStack and only want it taking a small amount of the space, rather than a full third. When I implement layoutPriority it decreases the horizontal space that the title area takes, but it no longer expands vertically if the title text takes up more space than the other elements in the HStack. If I remove the layoutPriority it expands vertically to display the full title text as I want but also takes up a full third of the HStack which I dont want. Is there a way I am missing to implement this? UIElement HStack{ EventTitleBackground(name:name).rotationEffect(.degrees(270)) .frame(minHeight: 0, maxHeight: .infinity) .frame(minWidth: 0, maxWidth: .infinity) .layoutPriority(2) Spacer() VStack(alignment: .leading){ Text(time) .font(.title) Spacer() Text("\(truncatedLatitude) \(truncatedLongitude)") .font(.title) Spacer() Text("Altitude: \(truncatedAltitude)") .font(.title) } .layoutPriority(4) Spacer() VStack(alignment: .leading){ HStack{ Text("BOBR: \(bobrLargeText)") .font(.title) Text(" \(bobrSmallText)") .font(.body) } Spacer() Text("Heading | Course: \(heading) | \(heading)") .font(.title) Spacer() Text("Groundspeed: \(groundSpeed)") .font(.title) } .layoutPriority(4) Spacer() }
I suggest you to check this simple example. By tap on rectangle you can rotate it and with sliders you can change the "width" of rectangles. To be honest, it works exactly as mentioned in Apple docs. import SwiftUI struct ContentView: View { #State var angle0 = Angle(degrees: 0) #State var angle1 = Angle(degrees: 0) #State var width0: CGFloat = 100 #State var width1: CGFloat = 100 var body: some View { VStack { HStack { Color.red.frame(width: width0, height: 50) .onTapGesture { self.angle0 += .degrees(90) } .rotationEffect(angle0).border(Color.blue) Color.green.frame(width: width1, height: 50) .onTapGesture { self.angle1 += .degrees(90) } .rotationEffect(angle1).border(Color.blue) Spacer() } Slider(value: $width0, in: 50 ... 200) { Text("red") } Slider(value: $width1, in: 50 ... 200) { Text("green") } }.padding() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } the result is probably far away from what did you expect ... Let change the red color part Color.red//.frame(width: width0, height: 50) .onTapGesture { self.angle0 += .degrees(90) } .rotationEffect(angle0).border(Color.blue).frame(width: 50, height: width0) and see the difference Solution for you could be something like import SwiftUI struct VerticalText: View { let text: String #Binding var size: CGSize var body: some View { Color.red.overlay( Text(text).padding().fixedSize() .background( GeometryReader { proxy -> Color in // avoid layout cycling!!! DispatchQueue.main.async { self.size = proxy.size } return Color.clear } ).rotationEffect(.degrees(-90)) ) .frame(width: size.height, height: size.width) .border(Color.green) } } struct ContentView: View { #State var size: CGSize = .zero var body: some View { HStack(alignment: .top) { VerticalText(text: "Hello, World!", size: $size) Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus semper eros non condimentum mattis. In hac habitasse platea dictumst. Mauris aliquam, enim eu vehicula sodales, odio enim faucibus eros, scelerisque interdum libero mi id elit. Donec auctor ipsum at dolor pellentesque, sed dapibus felis dignissim. Sed ac euismod purus, sed sollicitudin leo. Maecenas ipsum felis, ultrices a urna nec, dapibus viverra libero. Pellentesque quis est nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum luctus a est eget posuere.") }.frame(height: size.width) .border(Color.red).padding() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } See, that the size of VerticalText is stored in the parent, doesn't matter if you use it or not. Otherwise the parent will not layout properly.