I have created a carousel cards in SwiftUI, it is working on the DragGesture
I want to achieve same experience using scrollview i.e. same design and functionalities using scrollview instead of Drag-gesture
I have created Sample using scrollview but it has some limitation
Here is ScreenShot the upper carousel is using scrollview and lower one using Drag Gesture
import SwiftUI
struct Item: Identifiable {
var id: Int
var title: String
var color: Color
var isSelected: Bool
}
class Store: ObservableObject {
#Published var items: [Item]
let colors: [Color] = [.red, .orange, .blue, .teal, .mint, .green, .gray, .indigo,.red, .orange, .blue, .teal, .mint, .green, .gray, .indigo]
init() {
items = []
for i in 0...15 {
let new = Item(id: i, title: "Item \(i)", color: colors[i], isSelected: false)
items.append(new)
}
}
}
struct ContentView: View {
#StateObject var store = Store()
#State private var draggingItem = 0.0
#State var activeIndex: Int = 0
#State var selectedIndex: Int = 0
#State private var snappedItem = 0.0
let gridItems = [
GridItem(.flexible())
]
var body: some View {
VStack {
Spacer()
Text("Selected Index: \(store.items[selectedIndex].id)")
.fontWeight(.bold)
.padding()
Text("Acticted Index: \(activeIndex)")
.fontWeight(.bold)
.padding()
Spacer()
ScrollView(.horizontal, showsIndicators: false) {
ScrollViewReader { scrollview in
LazyHGrid(rows: gridItems, alignment: .center, spacing: 25) {
ForEach(0..<store.items.count, id: \.self) { index in
GeometryReader { proxy in
let scale = getScale(proxy: proxy)
ZStack() {
Circle()
.fill(store.items[index].color)
Text(store.items[index].title)
.font(.body)
.fontWeight(.light)
}.frame(width: 70, height: 70)
.onTapGesture {
withAnimation {
print("Color: ",store.items[index].color)
print("ID: ",store.items[index].id)
print("Title: ",store.items[index].title)
selectedIndex = index
draggingItem = Double(selectedIndex)
activeIndex = selectedIndex
}
}
.overlay(Circle()
.stroke(selectedIndex == index ? .black : .clear, lineWidth: selectedIndex == index ? 2 : 0))
.scrollSnappingAnchor(.bounds)
.scaleEffect(.init(width: (scale * 1.2) , height: (scale * 1.2)))
.animation(.easeOut(duration: 0.2), value: 0)
.padding(.vertical)
.onChange(of: selectedIndex) { newValue in
withAnimation {
scrollview.scrollTo(selectedIndex, anchor: .center)
}
}
.zIndex(1.0 - abs(distance(store.items[index].id)) * 0.1)
} //End Geometry
.frame(width: 70, height: 150)
} //End ForEach
} //End Grid
}
}
ZStack {
ForEach(0..<store.items.count, id: \.self) { index in
ZStack {
Circle()
.fill(store.items[index].color)
Text(store.items[index].title)
.padding()
}
.frame(width: 100, height: 100)
.onTapGesture { loc in
print("Color: ",store.items[index].color)
print("ID: ",store.items[index].id)
print("Title: ",store.items[index].title)
selectedIndex = index
withAnimation(.linear) {
draggingItem = Double(store.items[index].id)
activeIndex = index
}
}
.overlay(Circle()
.stroke(activeIndex == index ? .white : .clear, lineWidth: activeIndex == index ? 2 : 0))
.scaleEffect(1.0 - abs(distance(store.items[index].id)) * 0.15 )
.offset(x: myXOffset(store.items[index].id), y: 0)
.zIndex(1.0 - abs(distance(store.items[index].id)) * 0.1)
}
}
.gesture(
DragGesture()
.onChanged { value in
draggingItem = (snappedItem) + value.translation.width / 100
}
.onEnded { value in
withAnimation {
snappedItem = draggingItem
draggingItem = round(draggingItem).remainder(dividingBy: Double(store.items.count))
//Get the active Item index
self.activeIndex = store.items.count + Int(draggingItem)
if self.activeIndex > store.items.count || Int(draggingItem) >= 0 {
self.activeIndex = Int(draggingItem)
}
}
}
)
}
}
func distance(_ item: Int) -> Double {
return (draggingItem - Double(item)).remainder(dividingBy: Double(store.items.count))
}
func myXOffset(_ item: Int) -> Double {
let angle = Double.pi * 2 / Double(store.items.count) * distance(item)
return sin(angle) * 200
}
func getScale(proxy: GeometryProxy) -> CGFloat {
let midPoint: CGFloat = 200
let viewFrame = proxy.frame(in: CoordinateSpace.global)
var scale: CGFloat = 1.0
let deltaXAnimationThreshold: CGFloat = 70
let diffFromCenter = abs(midPoint - viewFrame.origin.x - deltaXAnimationThreshold / 2)
if diffFromCenter < deltaXAnimationThreshold {
scale = 1 + (deltaXAnimationThreshold - diffFromCenter) / 300
}
return scale
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
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
}
}
}
trying to bring data from LeadDetailUI to formUI to be able to edit the data in formUI and for the life of me can't figure this out, not sure on way to go (Bindings or environmentObject). eighthor way can't get it to work. I tried with bindings couldn't get it to work. please help with example on how to do this.
struct LeadDetailUI: View {
#ObservedObject var viewModel: getCustomerData
#State var tbl11 = ""
#State var tbl12 = ""
#State var tbl13 = ""
#State var tbl14 = ""
#State var tbl15 = ""
#State var tbl16 = ""
#State var tbl17 = ""
#State var tbl21 = ""
#State var tbl22 = 0
#State var tbl23 = 0
#State var tbl24 = 0
#State var tbl25 = 0
#State var tbl26 = ""
#State var tbl27 = ""
#State var l11 = ""
#State var l12 = ""
#State var l13 = ""
#State var l14 = ""
#State var l15 = ""
#State var l16 = ""
#State var l17 = ""
#State var l21 = ""
#State var l22 = ""
#State var l23 = ""
#State var l24 = ""
#State var l25 = ""
#State var l26 = ""
#State var l27 = ""
var body: some View {
NavigationView {
VStack() {
ScrollView(self.height > 700 ? .init() : .vertical, showsIndicators: true) {
VStack(alignment: .trailing, spacing: 13) {
HStack {
TextField("Peter Balsamo", text: $name).font(.title)
.padding(.top, 3)
.padding(.leading, 20)
.padding(.bottom, -10)
//.redacted(reason: .placeholder)
Text("Following").font(.headline)
.padding(.top, 10)
Button(action: {
}) {
Image(systemName: "star.fill")
.resizable()
.frame(width: 20, height: 20)
.foregroundColor(Color.orange)
.padding(.top, 7)
.padding(.trailing, 15)
}
}
Divider()
VStack {
HStack {
VStack(alignment: .leading, spacing: 5, content: {
TextField("Amount", text: $amount).font(.largeTitle)
.offset(y: -3)
TextField("Address", text: $address).font(.title3)
TextField("City", text: $city).font(.title3)
TextField("Sale Date:", text: $l1datetext).font(.caption2)
.padding(.top, 15)
TextField("Date:", text: $date).font(.headline)
.padding(.top, -5)
})
.padding(.bottom, 0)
.padding(.leading, 15)
Spacer()
VStack(alignment: .trailing, spacing: 0, content: {
Image("taylor_swift_profile")
.resizable()
.frame(width: 115, height: 115)
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 2))
.padding(.top, -25)
TextField("Lead#", text: $id).font(.caption2)
.multilineTextAlignment(.trailing)
.padding(.top, 15)
})
.frame(width: 120)
.padding(.trailing, 10)
Spacer()
}
HStack {
VStack(alignment: .leading, spacing: 0, content: {
HStack {
Toggle("", isOn: $showingSold.animation(.spring()))
.frame(width:80, height: 30)
.toggleStyle(SwitchToggleStyle(tint: .blue))
.clipShape(RoundedRectangle(cornerRadius: 10))
if showingSold {
Text("Priority").font(.headline)
.background(Color.red.cornerRadius(10))
.foregroundColor(.white)
.padding(.leading, 10)
}
}
})
Spacer()
Button(action: {
showFullscreen.toggle()
}) {
Text("Map")
.fontWeight(.bold)
.frame(width:115, height: 30)
.foregroundColor(.white)
.background(Color(.systemBlue))
.clipShape(RoundedRectangle(cornerRadius: 10))
}
.padding(.trailing, 20)
}
.padding(.bottom, 30)
}
.fullScreenCover(isPresented: $showFullscreen, content: {
HomeMap()
})
}
.foregroundColor(self.color == 0 ? Color.black : Color.white)
.background(self.color == 0 ? Color.yellow : Color.purple)
.clipShape(CustomShape(corner: .bottomLeft, radii: 55))
ScrollView(self.height > 800 ? .init() : .vertical, showsIndicators: false) {
let first = DataUI(name: tbl11, label: l11)
let second = DataUI(name: tbl12, label: l12)
let third = DataUI(name: tbl13, label: l13)
let fourth = DataUI(name: tbl14, label: l14)
let fifth = DataUI(name: tbl15, label: l15)
let sixth = DataUI(name: tbl16, label: l16)
let seventh = DataUI(name: tbl17, label: l17)
let eighth = DataUI(name: tbl21, label: l21)
let ninth = DataUI(name: "\(tbl22)", label: l22)
let tenth = DataUI(name: "\(tbl23)", label: l23)
let eleven = DataUI(name: "\(tbl24)", label: l24)
let twelve = DataUI(name: "\(tbl25)", label: l25)
let thirteen = DataUI(name: tbl26, label: l26)
let fourteen = DataUI(name: tbl27, label: l27)
let customers = [first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleven, twelve,thirteen, fourteen]
List(customers) { customer in
CenterViewUI(formData: customer)
}
}
.edgesIgnoringSafeArea(.all)
.statusBar(hidden: true)
.animation(.default)
BottomViewUI(comments: self.$comments, lnewsTitle: self.$lnewsTitle, index: $index)
}
.shadow(color: Color.white.opacity(0.2), radius: 5, x: 0, y: 2)
}
.navigationTitle("Profile")
.navigationBarHidden(false)
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems (
leading:
Button(action: {
showActionSheet.toggle()
}) {
Image(systemName: "square.and.arrow.up")
.resizable()
.frame(width: 20, height: 20)
},
trailing:
//NavigationLink(destination: FormUI(frm11: $tbl11)) {
Button(action: {
showSheet.toggle()
}, label: {
Text("Edit")
.actionSheet(isPresented: $showActionSheet, content: getActionSheet)
.sheet(isPresented: $showSheet, content: {
FormUI()
})
.foregroundColor(self.color == 0 ? Color.yellow : Color.purple)
}
}
struct DataUI: Identifiable {
var id = UUID().uuidString
var name: String
var label : String
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
Group {
LeadDetailUI(viewModel: getCustomerData())
.preferredColorScheme(.dark)
}
}
}
public struct FormUI: View {
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var viewModel: getCustomerData
private var db = Firestore.firestore()
#State var frm11 = ""
#State var frm12 = ""
#State var frm13 = ""
#State var frm14 = ""
#State var frm15 = ""
#State var frm16 = ""
#State var frm17 = ""
#State var frm18 = ""
#State var frm19 = ""
public var body: some View {
NavigationView {
VStack {
ScrollView(self.height > 800 ? .init() : .vertical, showsIndicators: false) {
VStack {
//ForEach(self.viewModel.data) { i in
Form {
Section {
HStack{
VStack{
Image("taylor_swift_profile")
.resizable()
.frame(width: 75, height: 75)
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 2))
.padding()
Button(action: {}, label: {
Text("Edit")
.font(.caption)
.padding(.top, -15)
.foregroundColor(self.color == 0 ? Color.purple : Color.red)
})
}
.padding(.leading, -30)
Divider()
Spacer()
VStack(spacing: 12) {
TextField("First", text: $frm11)
TextField("Last", text: $frm12)
Picker(selection: $selection, label:
TextField("Company", text: $callback)) {
ForEach(0 ..< pickContractor.count) {
Text(self.pickContractor[$0])
}
}
}
.font(.system(size: 20.0))
.multilineTextAlignment(.leading)
}
}
.font(.headline)
.padding(.leading, 18)
Section(header: Text("Customer Info")) {
HStack {
Text("Address:")
.formTextStyle()
Spacer()
TextField("address", text: $address)
.formStyle()
}
HStack {
Text("City:")
.formTextStyle()
Spacer()
TextField("city", text: $city)
.formStyle()
}
HStack {
Text("State:")
.formTextStyle()
.multilineTextAlignment(.leading)
Spacer()
TextField("state", text: $state)
.formStyle()
.frame(minWidth: 50, maxWidth: 60)
.autocapitalization(.allCharacters)
.multilineTextAlignment(.leading)
//.padding(.leading, )
Spacer()
Text("Zip:")
.formTextStyle()
TextField("zip", text: $zip)
.formStyle()
.frame(minWidth: 100, maxWidth: 145)
.keyboardType(.numberPad)
}
HStack {
Text("Phone:")
.formTextStyle()
Spacer()
TextField("phone", text: $phone)
.formStyle()
.keyboardType(.numberPad)
}
HStack {
Text("Amount:")
.formTextStyle()
Spacer()
Stepper(
onIncrement: {
stepperValue += 1000
},
onDecrement: {
stepperValue -= 1000
},
label: {
TextField("amount", text: $amount)
.formStyle()
})
}
HStack {
Text("Email:")
.formTextStyle()
Spacer()
TextField("email", text: $email)
.formStyle()
.keyboardType(.emailAddress)
}
}
Section {
HStack {
Text("Salesman:")
.formTextStyle()
Spacer()
TextField("salesman", text: $salesman)
}
HStack {
Text("Job:")
.formTextStyle()
Spacer()
TextField("job", text: $jobName)
.formStyle()
}
HStack {
Text("Product:")
.formTextStyle()
Spacer()
TextField("product", text: $adName)
.formStyle()
}
HStack {
Text("Quantity:")
.formTextStyle()
Spacer()
TextField("quantity", text: $frm25)
.formStyle()
.keyboardType(.numberPad)
}
HStack {
Text("Apt Date:")
.formTextStyle()
Spacer()
DatePicker(selection: $selDate, displayedComponents: .date) {
TextField("", text: $aptdate)
.formStyle()
}
}
HStack {
Text("Comments:")
.formTextStyle()
Spacer()
TextEditor(text: $comment)
}
}
Section(header: Text("Misc")) {
HStack {
Toggle(isOn: $isOn) {
Text("\(self.isOn == true ? "Active":"Not Active")")
.formTextStyle()
}
.toggleStyle(SwitchToggleStyle(tint: .purple))
}
HStack {
Text("Date")
.formTextStyle()
Spacer()
DatePicker(selection: $selDate, displayedComponents: .date, label: {
TextField("", text: $date)
})
}
HStack {
Text("Spouse")
.formTextStyle()
Spacer()
TextField("spouse", text: $spouse)
}
HStack {
Text("Called Back")
.formTextStyle()
Spacer()
Picker(selection: $selection, label:
TextField("", text: $callback)) {
ForEach(0 ..< callbackPicker.count) {
Text(self.callbackPicker[$0])
}
}
}
HStack {
Text("Rate")
.formTextStyle()
Spacer()
Picker(selection: $selection, label:
TextField("", text: $rate)) {
ForEach(0 ..< pickRate.count) {
Text(verbatim: self.pickRate[$0])
}
}
}
HStack {
Text("Photo")
.formTextStyle()
Spacer()
TextField("photo", text: $photo)
}
}
}
.font(.system(size: 20.0))
.padding(.top, -40)
}
}
}
.navigationTitle("Data Entry")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Image(systemName: "xmark.circle").font(.largeTitle)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
//if !frm11.isEmpty && !frm12.isEmpty {
saveData()
resetTextFields()
self.alert.toggle()
}, label: {
Text("Save")
}
}
.alert(isPresented: $alert) {
Alert(title: Text("Upload Complete"), message: Text("Successfully updated the data"), dismissButton: .default(Text("Ok")))
}
}
.accentColor(self.color == 0 ? Color.purple : Color.red)
}
private func saveData() {
let uid = Auth.auth().currentUser!.uid
var ref: DocumentReference? = nil
ref = db.collection("Customers").addDocument(data: [
"active": frm30,
"custId": frm12,
"custNo": custNo,
"leadNo": leadNo,
"first": frm11,
"lastname": frm12,
"contractor": frm13,
"city": city,
"state": state,
"zip": zip,
"phone": phone,
"amount": Int(amount) as Any,
"email": email,
"rate": rate,
"salesNo": saleNo,
"jobNo": jobNo,
"adNo": adNo,
"quan": Int(frm25) as Any,
"start": NSNull(),
"completion": NSNull(),
"lastUpdate": Timestamp(date: Date()),
"creationDate": Timestamp(date: Date()),
"aptdate": aptdate,
"comments": comment,
"spouse": spouse,
"photo": photo,
"uid": uid,
]) { error in
if let error = error {
print("Error adding document: \(error)")
} else {
print("Document added with ID: \(ref!.documentID)")
}
}
}
func updateData() {
db.collection("Customer")
.document()
.setData(["active":self.frm30,"custId":self.frm12,"custNo":self.custNo,"leadNo":self.leadNo,"first":self.frm11,"lastname":self.frm12,"contractor":self.frm13,"city":self.city,"state":self.state,"zip":self.zip,"phone":self.phone,"amount":self.amount,"email":self.email,"rate":self.rate,"salesNo":self.saleNo,"jobNo":self.jobNo,"adNo":self.adNo,"start":self.start,"lastUpdate":Timestamp(date:Date()),"aptdate":self.aptdate,"comments":self.comment,"spouse":self.spouse,"photo":self.photo]) { (error) in
if error != nil{
print((error?.localizedDescription)!)
return
}
//self.presentation.wrappedValue.dismiss()
}
}
}
struct FormUI_Previews: PreviewProvider {
static var previews: some View {
FormUI()
.preferredColorScheme(.dark)
}
}
I did my best to pull out only a small part of my larger project that displays this odd behavior. The intention is for one random number to be added to the array and displayed every 3 seconds. In iOS 13 each number slides in from the left every 3 seconds and everything works as expected. What I see in iOS 14 is that 4 numbers are added every 3 seconds. Does anyone understand why this would be happening? Thanks in advance!
import SwiftUI
struct ContentView: View {
#State private var calledNumbers = CalledNumbers()
#State private var timer = Timer.publish(every: 3, tolerance: 0.5, on: .main, in: .common).autoconnect()
#State private var inProgress = false
var body: some View {
Button(action: {
if !self.inProgress {
self.calledNumbers.startOver()
print("Start timer")
self.timer = Timer.publish(every: 3, tolerance: 0.5, on: .main, in: .common).autoconnect()
}
else {
print("Stop timer")
self.timer.upstream.connect().cancel()
}
self.inProgress.toggle()
})
{
if(self.inProgress == false) {
Text("S T A R T")
.font(.system(size: 22))
.fontWeight(.heavy)
.frame(width: 200, height: 35, alignment: .center)
.background(Capsule()
.fill(Color.green))
.cornerRadius(35)
.foregroundColor(.white)
.padding(.bottom, 2)
}
else {
Text("S T O P")
.font(.system(size: 22))
.fontWeight(.heavy)
.frame(width: 200, height: 35, alignment: .center)
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(35)
.onReceive(self.timer) { _ in
self.timer.upstream.connect().cancel()
print("CALL NEXT NUMBER")
self.calledNumbers.callNextNumber()
self.timer = Timer.publish(every: 3, tolerance: 0.5, on: .main, in: .common).autoconnect()
}
}
}
ZStack {
RoundedRectangle(cornerRadius: 35)
.frame(width: UIScreen.main.bounds.size.width - 19, height: 40, alignment: .center)
.foregroundColor(.clear)
.padding(.bottom, 2)
HStack {
ForEach(self.calledNumbers.calledNumberList.reversed().filter {self.checkCount(number: $0)}, id: \.self) { number in
Text("\(String(number))")
.font(.custom("Menlo", size: 20))
.fontWeight(.black)
.frame(width: 40, height: 40, alignment: .center)
.background(Color.red)
.clipShape(Circle())
.foregroundColor(.white)
.transition(AnyTransition.offset(x: (number == self.calledNumbers.calledNumberList.last) ? -250 : 250))
.animation(Animation.linear(duration: 1).repeatCount(1))
}
}
}
}
func checkCount(number: Int) -> Bool {
let count = self.calledNumbers.calledNumberList.count
if (count <= 8) {
return true
}
else {
guard let index = self.calledNumbers.calledNumberList.firstIndex(of: number) else { return false }
if (count - index > 8) { return false }
else { return true }
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This file was added by xcode 12 (I named this project Test2):
import SwiftUI
#main
struct Test2App: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Class for calledNumbers
//
// class.swift
// Test2
//
import Foundation
class CalledNumbers {
#Published var calledNumberList: [Int]
init() {
calledNumberList = [Int]()
}
func callNextNumber() {
var tempNumber = Int.random(in: 1...75)
while calledNumberList.contains(tempNumber) {
tempNumber = Int.random(in: 1...75)
}
calledNumberList.append(tempNumber)
print("Number added \(tempNumber)")
}
func startOver() {
calledNumberList.removeAll()
}
}
The problem seems to be caused by having the .onReceived() attached to the code inside of the Button. Moving .onReceived() to the Button as a whole solves the issue.
Also, you were doing more timer manipulation than is necessary. I removed stopping and restarting the timer from .onReceive().
calledNumbers should be an #ObservableObject.
struct ContentView: View {
#ObservedObject var calledNumbers = CalledNumbers()
#State private var timer = Timer.publish(every: 3, tolerance: 0.5, on: .main, in: .common).autoconnect()
#State private var inProgress = false
var body: some View {
Button(action: {
if !self.inProgress {
self.calledNumbers.startOver()
print("Start timer")
self.timer = Timer.publish(every: 3, tolerance: 0.5, on: .main, in: .common).autoconnect()
}
else {
print("Stop timer")
self.timer.upstream.connect().cancel()
}
self.inProgress.toggle()
})
{
Text(inProgress ? "STOP" : "START")
.font(.system(size: 22))
.fontWeight(.heavy)
.frame(width: 200, height: 35, alignment: .center)
.cornerRadius(35)
.foregroundColor(.white)
.background(Capsule().fill(inProgress ? Color.red : .green))
.padding(.bottom, 2)
}
}
.onReceive(self.timer) { _ in
print("CALL NEXT NUMBER")
self.calledNumbers.callNextNumber()
}
.onAppear {
// Cancel the initial timer
self.timer.upstream.connect().cancel()
}
ZStack {
RoundedRectangle(cornerRadius: 35)
.frame(width: UIScreen.main.bounds.size.width - 19, height: 40, alignment: .center)
.foregroundColor(.clear)
.padding(.bottom, 2)
HStack {
ForEach(self.calledNumbers.calledNumberList.reversed().filter {self.checkCount(number: $0)}, id: \.self) { number in
Text("\(String(number))")
.font(.custom("Menlo", size: 20))
.fontWeight(.black)
.frame(width: 40, height: 40, alignment: .center)
.background(Color.red)
.clipShape(Circle())
.foregroundColor(.white)
.transition(AnyTransition.offset(x: (number == self.calledNumbers.calledNumberList.last) ? -250 : 250))
.animation(Animation.linear(duration: 1).repeatCount(1))
}
}
}
}
func checkCount(number: Int) -> Bool {
let count = self.calledNumbers.calledNumberList.count
if (count <= 8) {
return true
}
else {
guard let index = self.calledNumbers.calledNumberList.firstIndex(of: number) else { return false }
if (count - index > 8) { return false }
else { return true }
}
}
}
Also, your CalledNumbers class should be an ObservableObject so that Published works correctly:
import Foundation
class CalledNumbers: ObservableObject {
#Published var calledNumberList: [Int]
init() {
calledNumberList = [Int]()
}
func callNextNumber() {
var tempNumber = Int.random(in: 1...75)
while calledNumberList.contains(tempNumber) {
tempNumber = Int.random(in: 1...75)
}
calledNumberList.append(tempNumber)
print("Number added \(tempNumber)")
}
func startOver() {
calledNumberList.removeAll()
}
}
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
}
}