Pass to Another View after Success View Appears - swiftui

After seeing the popup view that says your credit card saved successfully. I want to see this popup for 2-3 seconds then to pass another view called AddressView. Maybe it is irrelevant but I also added that popup view and the name is SuccessCardView.
#State private(set) var successAlert = false
ZStack {
HStack {
Button(action: {
self.isListTapped.toggle()
}, label: { CustomButton(title: "Listeden Sec", icon: .none, status: .enable, width: 150)})
Button(action: {
self.isSaved.toggle()
self.creditCards.append(self.creditCard)
print(self.creditCards[0].cardNumber)
if self.creditCards[0].cardNumber == "" {
self.showingAlert = true
} else if self.creditCards[0].cardNumber.count == 16 {
self.successAlert = true
}
}, label: { CustomButton(title: "Kaydet", icon: .none, status: .enable, width: 150)})
.alert(isPresented: $showingAlert) {
Alert(title: Text("Kart Bilgileri Hatali"), message: Text("Tekrar Kontrol Edin"), dismissButton: .default(Text("Got it!")))
}
}
SuccessCardView(isShown: $successAlert) // I want to show that view than jump to another view
}
struct SuccessCardView: View {
#Binding var isShown: Bool
#State var viewState = CGSize.zero
var body: some View {
VStack {
ZStack {
Rectangle()
.foregroundColor(Color(#colorLiteral(red: 0, green: 0.6588235294, blue: 0.5254901961, alpha: 1)))
.cornerRadius(10)
.frame(width: 355, height: 76)
HStack {
VStack(alignment: .leading) {
Text("Tebrikler!")
.font(Font.custom("SFCompactDisplay-Bold", size: 16))
.foregroundColor(.white)
Text("Kart Basariyla Eklendi")
.font(Font.custom("SFCompactDisplay", size: 14))
.foregroundColor(.white)
}
.offset(x: -70)
}
}
Spacer()
}
.offset(y: isShown ? .zero : -UIScreen.main.bounds.size.height)
.offset(y: viewState.height )
.animation(.spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0))
.offset(y: -100)
}
}

It is not clear where/how is AddressView configured, but you can do the following
} else if self.creditCards[0].cardNumber.count == 16 {
self.successAlert = true
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.successAlert = false // hide popup
self.showAddressView = true // eg. activate navigation link
}
}

Related

resizable header in SwiftUI

I was trying to make resizable header but it breaks.
I am trying to clone twitter profile,
I think logic is right but can I know why this one is not working?
I made HStack and try to hide it but when I scroll back it can't come back.
Tried with GeometryReader
Please help me, thanks
import SwiftUI
struct ProfileView: View {
// change views
#State var isHide = false
#State private var selectionFilter: TweetFilterViewModel = .tweets
#Namespace var animation
var body: some View {
VStack(alignment: .leading) {
//hiding
if isHide == false {
headerView
actionButtons
userInfoDetails
}
tweetFilterBar
.padding(0)
ScrollView(showsIndicators: false) {
LazyVStack {
GeometryReader { reader -> AnyView in
let yAxis = reader.frame(in: .global).minY
let height = UIScreen.main.bounds.height / 2
if yAxis < height && !isHide {
DispatchQueue.main.async {
withAnimation {
isHide = true
}
}
}
if yAxis > 0 && isHide {
DispatchQueue.main.async {
withAnimation {
isHide = false
}
}
}
return AnyView(
Text("")
.frame(width: 0, height: 0)
)
}
.frame(width: 0, height: 0)
ForEach(0...9, id: \.self) { _ in
TweetRowView()
}
}
}
Spacer()
}
}
}
struct ProfileView_Previews: PreviewProvider {
static var previews: some View {
ProfileView()
}
}
extension ProfileView {
var headerView: some View {
ZStack(alignment: .bottomLeading) {
Color(.systemBlue)
.ignoresSafeArea()
VStack {
Button {
} label: {
Image(systemName: "arrow.left")
.resizable()
.frame(width: 20, height: 16)
.foregroundColor(.white)
.position(x: 30, y: 12)
}
}
Circle()
.frame(width: 72, height: 72)
.offset(x: 16, y: 24)
}.frame(height: 96)
}
var actionButtons: some View {
HStack(spacing: 12){
Spacer()
Image(systemName: "bell.badge")
.font(.title3)
.padding(6)
.overlay(Circle().stroke(Color.gray, lineWidth: 0.75))
Button {
} label: {
Text("Edit Profile")
.font(.subheadline).bold()
.accentColor(.black)
.padding(10)
.overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.gray, lineWidth: 0.75))
}
}
.padding(.trailing)
}
var userInfoDetails: some View {
VStack(alignment: .leading) {
HStack(spacing: 4) {
Text("Heath Legdet")
.font(.title2).bold()
Image(systemName: "checkmark.seal.fill")
.foregroundColor(Color(.systemBlue))
}
.padding(.bottom, 2)
Text("#joker")
.font(.subheadline)
.foregroundColor(.gray)
Text("Your mom`s favorite villain")
.font(.subheadline)
.padding(.vertical)
HStack(spacing: 24) {
Label("Gothem.NY", systemImage: "mappin.and.ellipse")
Label("www.thejoker.com", systemImage: "link")
}
.font(.caption)
.foregroundColor(.gray)
HStack(spacing: 24) {
HStack {
Text("807")
.font(.subheadline)
.bold()
Text("following")
.font(.caption)
.foregroundColor(.gray)
}
HStack {
Text("200")
.font(.subheadline)
.bold()
Text("followers")
.font(.caption)
.foregroundColor(.gray)
}
}
.padding(.vertical)
}
.padding(.horizontal)
}
var tweetFilterBar: some View {
HStack {
ForEach(TweetFilterViewModel.allCases, id: \.rawValue) { item in
VStack {
Text(item.title)
.font(.subheadline)
.fontWeight(selectionFilter == item ? .semibold : .regular)
.foregroundColor(selectionFilter == item ? .black : .gray)
ZStack {
Capsule()
.fill(Color(.clear))
.frame(height: 3)
if selectionFilter == item {
Capsule()
.fill(Color(.systemBlue))
.frame(height: 3)
.matchedGeometryEffect(id: "filter", in: animation)
}
}
}
.onTapGesture {
withAnimation(.easeInOut) {
self.selectionFilter = item
}
}
}
}
}
}
While the animation between show and hide is running, the GeometryReader is still calculating values – which lets the view jump between show and hide – and gridlock.
You can introduce a new #State var isInTransition = false that checks if a show/hide animation is in progress and check for that. You set it to true at the beginning of the animation and to false 0.5 secs later.
Also I believe the switch height is not exactly 1/2 of the screen size.
So add a new state var:
#State var isInTransition = false
and in GeometryReader add:
GeometryReader { reader -> AnyView in
let yAxis = reader.frame(in: .global).minY
let height = UIScreen.main.bounds.height / 2
if yAxis < 350 && !isHide && !isInTransition {
DispatchQueue.main.async {
isInTransition = true
withAnimation {
isHide = true
}
}
// wait for animation to finish
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
isInTransition = false
}
} else if yAxis > 0 && isHide && !isInTransition {
DispatchQueue.main.async {
isInTransition = true
withAnimation {
isHide = false
}
}
// wait for animation to finish
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
isInTransition = false
}
}
return AnyView(
// displaying values for test purpose
Text("\(yAxis) - \(isInTransition ? "true" : "false")").foregroundColor(.red)
// .frame(width: 0, height: 0)
)
}

SwiftUI : How I can hide the navigationBar when the keyboard is shown

enter image description here
Please check attached image file, that is situation of mine.
I tried to make this view, but when the keyboard is shown. The main view and navigationBar is mixed.
I hope to hide the navigationBar when I touched the textfield and keyboard is shown.
How I treat that? Thank you all
This is source code below.
import SwiftUI
struct FlashCardView: View {
#EnvironmentObject var itemModel : ItemModel
var item : Item
#State var isGeneral : Bool = true
#State var inputAnswer : String = ""
#State var showAlert : Bool = false
#State var isAnswer : Bool = false
#State var randomWord : (String, String) = ("", "")
var body: some View {
VStack {
HStack {
if let group = item.group {
Text("Game with ' \(group) '")
.font(.title3.bold())
.lineLimit(1)
.padding()
.background(
Color.yellow
.frame(height : 4)
.offset(y : 24)
)
}
}
HStack {
Button(action: {
isGeneral = true
makeNewCard()
}, label: {
Text(isGeneral ? "General 🔥" : "General")
.font(.headline)
.foregroundColor(isGeneral ? .white : .black)
.frame(width : 140, height : 50)
.background(isGeneral ? .blue : .gray)
.cornerRadius(10)
.padding()
})
.shadow(color: .gray.opacity(0.5), radius: 3, x: 3, y: 3)
Spacer()
Button(action: {
isGeneral = false
makeNewCard()
}, label: {
Text(!isGeneral ? "Favorite 🔥" : "Favorite")
.font(.headline)
.foregroundColor(!isGeneral ? .white : .black)
.frame(width : 140, height : 50)
.multilineTextAlignment(.center)
.background(!isGeneral ? .blue : .gray)
.cornerRadius(10)
.padding()
})
.disabled(item.children.filter({$0.isFavorite}).isEmpty)
.shadow(color: .gray.opacity(0.5), radius: 3, x: 3, y: 3)
}
Text(randomWord.0)
.font(.title2.bold())
.frame(maxWidth : .infinity)
.frame(height : UIScreen.main.bounds.height*0.33)
.multilineTextAlignment(.center)
.background(.ultraThinMaterial)
.cornerRadius(20)
.shadow(color: .gray.opacity(0.4), radius: 3, x: 3, y: 3)
.padding()
TextField("What is the answer?", text: $inputAnswer)
.frame(maxWidth : .infinity)
.frame(height : 60)
.font(.body)
.multilineTextAlignment(.center)
.autocapitalization(.none)
.submitLabel(.done)
.onSubmit {
if randomWord.1 == inputAnswer {
self.showAlert.toggle()
self.isAnswer = true
self.inputAnswer = ""
} else {
self.showAlert.toggle()
self.isAnswer = false
self.inputAnswer = ""
}
}
Divider()
.padding(.horizontal)
.padding(.vertical, -10)
Button(action: {
if randomWord.1 == inputAnswer {
self.showAlert.toggle()
self.isAnswer = true
self.inputAnswer = ""
} else {
self.showAlert.toggle()
self.isAnswer = false
self.inputAnswer = ""
}
}, label: {
Label("Check", systemImage: "checkmark.rectangle.fill")
.frame(maxWidth : .infinity)
.frame(height : 60)
.font(.headline)
.foregroundColor(.white)
.multilineTextAlignment(.center)
.background(Color.green)
.cornerRadius(10)
.shadow(color: .gray.opacity(0.4), radius: 3, x: 3, y: 3)
.padding()
})
.alert(isPresented : $showAlert) {
Alert(title: Text(isAnswer ? "Nice! that is answer!" : "Sorry, It was not answer.."), message: Text(isAnswer ? "You got an answer! Cool! 🥰" : "It's OK!, Keep studying! 😋"), dismissButton: .default(Text("OK")) {
makeNewCard()
})
}
.disabled(inputAnswer.count == 0)
} // vst
.padding()
.onAppear {
makeNewCard()
}
.navigationTitle("Flashcard Game 🎲")
}
}
extension FlashCardView {
func makeNewCard() {
if isGeneral == true {
randomWord = itemModel.makeRandomChildren(item: item)
} else {
randomWord = itemModel.makeRandomFavoriteChildren(item: item)
}
}
}
Add onEditingChanged to the keyboard and add a conditional to the navBar.
If the navBar doesn't hide when the user answers the question, add another toggle to onCommit.
import SwiftUI
struct ContentView: View {
#State var inputAnswer: String = ""
#State var isTyping: Bool = false
var body: some View {
NavigationView {
VStack(alignment: .center) {
Text("Hide navbar when user interacts with the textField")
.padding()
TextField("What is the answer?", text: $inputAnswer, onEditingChanged: {
self.isTyping = $0 // <= Toggle boolean if user interacts with the textField
})
.keyboardType(.default)
}
.navigationTitle("Home")
.navigationBarHidden(isTyping ? true : false) // <= hide on the condition of the boolean
}
}
}

Can't sheet with button in ListView SwiftUI

I have a list. In the List there are 2 buttons.
I want to click on each button to present another view with Sheet.
When I first click it it works, but the second time or another tap button it doesn't present the view. Hope you can help me.
My code
My design
Read up on how to use Buttons and sheets. Typically Buttons triggering a sheet is used like this:
EDIT: with suggestion from Philip Dukhov, replace:
Button("Learning") {
}.sheet(isPresented: $isLearning, content: {
LearningView()
})
.onTapGesture {
self.isLearning = true
}
with:
Button(action: {isLearning = true}) {
Text("Learning")
}
.sheet(isPresented: $isLearning) {
LearningView()
}
and do the same for "Test" button, with "isTest" instead of "isLearning" and "TestViewCategory()" instead of "LearningView()".
EDIT 2:
Update "TestView" with:
struct TestView: View {
var title = ""
let models = modelItems
var body: some View {
ScrollView {
VStack {
ForEach(models) { model in
TopicList(model: model)
}
}
}
.navigationBarTitle(title, displayMode: .inline)
.onAppear {
UITableView.appearance().separatorStyle = .none
}
.animation(.spring())
}
}
EDIT 3: here is the test code that works for me:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
TestView()
}
}
}
struct ButtonView: View {
#State var isLearning: Bool = false
#State var isTest: Bool = false
var body: some View {
HStack {
Button(action: {isLearning = true}) {
Text("Learning")
}
.sheet(isPresented: $isLearning) {
Text("LearningView")
}
.font(.system(size: 16))
.frame(width: 132 , height: 48)
.background(Color(.white))
.overlay(
RoundedRectangle(cornerRadius: 30)
.stroke(Color(#colorLiteral(red: 0.9259920716, green: 0.9261471629, blue: 0.9259716272, alpha: 1)), lineWidth: 1)
)
Button(action: {isTest = true}) {
Text("Test")
}
.sheet(isPresented: $isTest) {
Text("TestViewCategory")
}
.font(.system(size: 16))
.frame(width: 132 , height: 48)
.background(Color(.white))
.overlay(
RoundedRectangle(cornerRadius: 30)
.stroke(Color(#colorLiteral(red: 0.9259920716, green: 0.9261471629, blue: 0.9259716272, alpha: 1)), lineWidth: 1)
)
}
}
}
struct TopicList: View {
// for testing
let model: String
#State private var showSubItem = false
var body: some View {
VStack {
HStack {
Image(systemName: showSubItem ? "arrow.up.circle" : "arrow.down.circle")
.resizable()
.frame(width: 26, height: 26)
.onTapGesture {
withAnimation {
showSubItem.toggle()
}
}
VStack {
VStack(alignment: .leading) {
Text("title")
.font(.custom("Poppins-Regular", size: 24))
.padding(.top,9)
.padding(.bottom,4)
HStack {
Text("Open date")
.font(.custom("Poppins-Regular", size: 12))
Text("Open date")
.font(.custom("Poppins-Regular", size: 12))
Text("Due date")
.font(.custom("Poppins-Regular", size: 12))
Text("Due date")
.font(.custom("Poppins-Regular", size: 12))
}
}
.padding(.leading,17)
.frame(width: 320, height: 70)
.fixedSize(horizontal: false, vertical: true)
if showSubItem {
ButtonView()
.padding(.top,12)
.fixedSize(horizontal: false, vertical: true)
.transition(.opacity)
.transition(.slide)
.padding(.bottom,13)
}
}
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color(#colorLiteral(red: 0.9259920716, green: 0.9261471629, blue: 0.9259716272, alpha: 1)), lineWidth: 1)
)
}
}
}
}
struct TestView: View {
var title = "nav title"
let models = ["1","2","3"]
var body: some View {
ScrollView {
VStack {
ForEach(models, id: \.self) { model in
TopicList(model: model)
}
}
}
.navigationBarTitle(title, displayMode: .inline)
.onAppear {
UITableView.appearance().separatorStyle = .none
}
.animation(.spring())
}
}

Tap gesture on buttons response randomly in SwiftUI

I have a My Profile form in my app. In the form I have some buttons. Among them 5 buttons are used to present custom drop down menus to select Sax, Blood, Nationality, and so on. However, when I tap these buttons sometime they response some time don't. For example, if I tap them 10 time they response 2 or 3 times. Some time they work for long tap. I have spent days on this matter, but could not identify any problem in my code. Is there anyone who faced this sort of problems? Could anyone figure out what is the actual problem. By the way, all other buttons in my app working absolutely fine.
This is the code for first 3 buttons in red rectangle in the picture
import SwiftUI
struct FormPartTwoView: View {
#Binding var gender: String
#Binding var blood: String
#Binding var nationality: String
#Binding var showingGenderPicker: Bool
#Binding var showingBloodGroupPicker: Bool
#Binding var showingNationalityPicker: Bool
#ObservedObject var userProfile = UserProfileViewModel()
#ObservedObject var userProfileUpdate = UserProfileUpdateViewModel()
var body: some View {
GeometryReader { geometry in
HStack {
Button(action: {
self.showingGenderPicker.toggle()
self.showingBloodGroupPicker = false
self.showingNationalityPicker = false
}) {
VStack {
TextField("Gender", text: self.$gender)
.padding(.horizontal)
.disabled(true)
}
.font(.system(size: 13))
.frame(height: 40)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color("Border2"), lineWidth: 1)
)
}
.buttonStyle(PlainButtonStyle())
Button(action: {
self.showingBloodGroupPicker.toggle()
self.showingGenderPicker = false
self.showingNationalityPicker = false
}) {
VStack {
TextField("Blood", text: self.$blood)
.padding(.horizontal)
.disabled(true)
}
.font(.system(size: 13))
.frame(height: 40)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color("Border2"), lineWidth: 1)
)
}
.buttonStyle(PlainButtonStyle())
Button(action: {
self.showingNationalityPicker.toggle()
self.showingGenderPicker = false
self.showingBloodGroupPicker = false
}) {
VStack {
TextField("Nationality", text: self.$nationality)
.padding(.horizontal)
.disabled(true)
}
.font(.system(size: 13))
.frame(height: 40)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color("Border2"), lineWidth: 1)
)
}
.buttonStyle(PlainButtonStyle())
}
.frame(width: geometry.size.width, height: 40)
}
}
}
struct FormPartTwoView_Previews: PreviewProvider {
static var previews: some View {
FormPartTwoView(gender: .constant(""), blood: .constant(""), nationality: .constant(""), showingGenderPicker: .constant(false), showingBloodGroupPicker: .constant(false), showingNationalityPicker: .constant(false))
}
}
This is the code for second tow Buttons:
import SwiftUI
struct FormPartFiveView: View {
#Binding var district: String
#Binding var upazila: String
#Binding var postcode: String
#Binding var showingUpazilaPicker: Bool
#Binding var showingDistrictPicker: Bool
#ObservedObject var userProfileUpdate = UserProfileUpdateViewModel()
var body: some View {
GeometryReader { geometry in
HStack {
Button(action: {
self.showingDistrictPicker.toggle()
self.showingUpazilaPicker = false
}) {
VStack {
Text("\(self.district == "" ? "District" : self.district)")
.foregroundColor(self.district == "" ? Color.gray : Color.black)
.padding(.horizontal)
}
.font(.system(size: 13))
.frame(width: geometry.size.width/3.2, height: 40)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color("Border2"), lineWidth: 1)
)
}
.buttonStyle(PlainButtonStyle())
Button(action: {
self.showingUpazilaPicker.toggle()
self.showingDistrictPicker = false
}) {
VStack {
Text("\(self.upazila == "" ? "Upazila" : self.upazila)")
.foregroundColor(self.upazila == "" ? Color.gray : Color.black)
.padding(.horizontal)
}
.font(.system(size: 13))
.frame(width: geometry.size.width/3.2, height: 40)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color("Border2"), lineWidth: 1)
)
}
.buttonStyle(PlainButtonStyle())
VStack {
TextField("Postcode", text: self.$postcode)
.padding(.horizontal)
}
.font(.system(size: 13))
.frame(width: 100, height: 40)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color("Border2"), lineWidth: 1)
)
}
.frame(width: geometry.size.width, height: 40)
}
}
}
struct FormPartFiveView_Previews: PreviewProvider {
static var previews: some View {
FormPartFiveView(district: .constant(""), upazila: .constant(""), postcode: .constant(""), showingUpazilaPicker: .constant(false), showingDistrictPicker: .constant(false))
}
}

SwiftUI multiline text always truncating at 1 line

I am trying to create a list of options for a user to choose from. The debug preview shows the general look and feel. My problem is that passing nil to .lineLimit in MultipleChoiceOption doesn't allow the text to grow beyond 1 line. How can I correct this?
struct Card<Content: View> : View {
private let content: () -> Content
init(content: #escaping () -> Content) {
self.content = content
}
private let shadowColor = Color(red: 69 / 255, green: 81 / 255, blue: 84 / 255, opacity: 0.1)
var body: some View {
ZStack {
self.content()
.padding()
.background(RoundedRectangle(cornerRadius: 22, style: .continuous)
.foregroundColor(.white)
.shadow(color: shadowColor, radius: 10, x: 0, y: 5)
)
}
.aspectRatio(0.544, contentMode: .fit)
.padding()
}
}
struct MultipleChoiceOption : View {
var option: String
#State var isSelected: Bool
var body: some View {
ZStack {
Rectangle()
.foregroundColor(self.isSelected ? .gray : .white)
.cornerRadius(6)
.border(Color.gray, width: 1, cornerRadius: 6)
Text(self.option)
.font(.body)
.foregroundColor(self.isSelected ? .white : .black)
.padding()
.multilineTextAlignment(.leading)
.lineLimit(nil)
}
}
}
struct MultipleChoice : View {
#State var selectedIndex = 1
var options: [String] = [
"Hello World",
"How are you?",
"This is a longer test This is a longer test This is a longer test This is a longer test This is a longer test This is a longer test"
]
var body: some View {
GeometryReader { geometry in
ScrollView {
VStack(alignment: .leading, spacing: 12) {
ForEach(self.options.indices) { i in
MultipleChoiceOption(option: self.options[i],
isSelected: i == self.selectedIndex)
.tapAction { self.selectedIndex = i }
}
}
.frame(width: geometry.size.width)
}
}
.padding()
}
}
struct MultipleChoiceCard : View {
var question: String = "Is this a question?"
var body: some View {
Card {
VStack(spacing: 30) {
Text(self.question)
MultipleChoice()
}
}
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
// NavigationView {
VStack {
MultipleChoiceCard()
Button(action: {
}) {
Text("Next")
.padding()
.foregroundColor(.white)
.background(Color.orange)
.cornerRadius(10)
}
}
.padding()
// .navigationBarTitle(Text("Hello"))
// }
}
}
#endif
Please see this answer for Xcode 11 GM:
https://stackoverflow.com/a/56604599/30602
Summary: add .fixedSize(horizontal: false, vertical: true) — this worked for me in my use case.
The modifier fixedSize() prevents the truncation of multiline text.
Inside HStack
Text("text").fixedSize(horizontal: false, vertical: true)
Inside VStack
Text("text").fixedSize(horizontal: true, vertical: false)
There is currently a bug in SwiftUI causing the nil lineLimit to not work.
If you MUST fix this now, you can wrap a UITextField:
https://icalvin.dev/post/403
I had the same problem and used this workaround:
Add the modifier:
.frame(idealHeight: .infinity)