SwiftUI Strange/unexpected behaviour when deleting elements in array of observedObjects - arrays

I am trying to create an editable 'parts list' in an app I'm trying to develop. It's been a rough road getting this far - many unexpected hurdles and obstacles, most of which have now been overcome (thanks to 'krjw').
So, I'm now at a stage where items can be added to a dynamic list with editable fields. The problem arises after an item is deleted...subsequent additions to the list (equal to the number of deletions) cannot be edited.
I've bundled the relevant code together for convenience below:
import SwiftUI
// Thanks to KRJW on StackOverflow for help getting this far...
struct PartEditView: View {
#ObservedObject var partToEdit:Part
var body: some View {
VStack{
HStack(alignment: .bottom){
TextField("Description", text: $partToEdit.description)
.frame(width: 350)
.padding(.all,5)
.cornerRadius(2)
.overlay(
RoundedRectangle(cornerRadius: 4)
.stroke(Color.red, lineWidth: 1))
Spacer()
TextField("Price", text: $partToEdit.price).keyboardType(.decimalPad)
.frame(width: 80)
.padding(.all,5)
.cornerRadius(2)
.overlay(
RoundedRectangle(cornerRadius: 4)
.stroke(Color.red, lineWidth: 1))
Spacer()
TextField("Quantity", text: $partToEdit.qty).keyboardType(.decimalPad)
.frame(width: 60)
.padding(.all,5)
.cornerRadius(2)
.overlay(
RoundedRectangle(cornerRadius: 4)
.stroke(Color.red, lineWidth: 1))
Spacer()
VStack(alignment: .leading){
//Text("Line total").font(.caption).padding(.bottom,10)
Text(String(format: "%.2f", Double(partToEdit.linePrice)))
.frame(width:90,alignment: .leading)
.padding(.all,5)
.cornerRadius(4)
.overlay(
RoundedRectangle(cornerRadius: 4)
.stroke(Color.blue, lineWidth: 1))
}
}
}
}
}
class Part: ObservableObject, Identifiable, Equatable {
static func == (lhs: Part, rhs: Part) -> Bool {
return lhs.id == rhs.id
}
//part line item (desc, price, qty, line price)
#Published var id: UUID
#Published var description:String
#Published var price:String
#Published var qty:String
var linePrice:Double{
let itemPrice = Double(price) ?? 0
let quantity = Double(qty) ?? 0
return itemPrice * quantity
}
init(id:UUID, description:String, price:String, qty:String) {
self.id = id
self.description = description
self.price = price
self.qty = qty
}
}
struct ContentView: View {
#State var parts = [Part]()
var body: some View {
VStack{
HStack{
Text("Add line ")
Image(systemName: "plus.circle").font(.largeTitle)
.onTapGesture {
let newPart:Part = Part(id: UUID(), description: "any.....thing", price: "", qty: "1")
self.parts.append(newPart)
}
}
List{
ForEach(parts){part in
PartEditView(partToEdit: part)
}.onDelete(perform: deleteRow)
}
HStack{
Spacer()
Text("Total: ")
Text(String(self.parts.reduce(0){$0 + $1.linePrice}))
}
Spacer()
}
}
func deleteRow(at offsets: IndexSet){
self.parts.remove(atOffsets: offsets)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I suspect that array indexing is somehow getting mixed up.
If anyone can shed any light on this I'd very much appreciate it.

Looks like the row doesn't get updated. I've solved it by adding an id to your PartEditView HStack that has all the TextFields, like so:
struct PartEditView: View {
#ObservedObject var partToEdit:Part
var body: some View {
VStack{
HStack(alignment: .bottom){
TextField("Description", text: $partToEdit.description)
.frame(width: 350)
.padding(.all,5)
.cornerRadius(2)
.overlay(
RoundedRectangle(cornerRadius: 4)
.stroke(Color.red, lineWidth: 1))
Spacer()
TextField("Price", text: $partToEdit.price).keyboardType(.decimalPad)
.frame(width: 80)
.padding(.all,5)
.cornerRadius(2)
.overlay(
RoundedRectangle(cornerRadius: 4)
.stroke(Color.red, lineWidth: 1))
Spacer()
TextField("Quantity", text: $partToEdit.qty).keyboardType(.decimalPad)
.frame(width: 60)
.padding(.all,5)
.cornerRadius(2)
.overlay(
RoundedRectangle(cornerRadius: 4)
.stroke(Color.red, lineWidth: 1))
Spacer()
VStack(alignment: .leading){
//Text("Line total").font(.caption).padding(.bottom,10)
Text(String(format: "%.2f", Double(partToEdit.linePrice)))
.frame(width:90,alignment: .leading)
.padding(.all,5)
.cornerRadius(4)
.overlay(
RoundedRectangle(cornerRadius: 4)
.stroke(Color.blue, lineWidth: 1))
}
}
.id(partToEdit.id)
}
}
}
This way the row will be updated for each Part. The previously deleted row won't be re-used by SwiftUI, instead a new one will be created.

Related

SwiftUI delete rows from a list

As you can see from the image I have a list of colors, I would like to be able to also give the possibility to delete a color from the array.
I tried to add a list and then call the onDelete call on ForEach, but it's not working well it gives me problems.
Then in addition to this I would like the list to be the size of the contained elements.
Error:
Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444
Can anyone give me some advice?
Code:
import SwiftUI
struct ContentView: View {
var cornerRadius: CGFloat = 16
#State public var select = 2
#State public var bgColors: [Color] =
[
Color(red: 21.0/255.0, green: 101.0/255.0, blue: 192.0/255.0),
Color(red: 255.0/255.0, green: 193.0/255.0, blue: 7.0/255.0),
Color(red: 76.0/255.0, green: 175.0/255.0, blue: 80.0/255.0)
]
#Environment(\.colorScheme) var colorScheme
#State var isShowPicker: Bool = false
#State var image: Image? = Image("placeholder")
#State private var url: String = "https://a.wattpad.com/useravatar/climaxmite.256.718018.jpg"
init() {
// Segmented control colors
UISegmentedControl.appearance().backgroundColor = .systemGray6
UISegmentedControl.appearance().selectedSegmentTintColor = UIColor(Color.blue)
UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.systemBackground], for: .selected)
UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.label], for: .normal)
}
var body: some View {
VStack{
ZStack {
RoundedRectangle(cornerRadius: cornerRadius)
.frame(width: UIScreen.main.bounds.width-40, height: 100, alignment: .center)
.foregroundColor(colorScheme == .dark ? .black : .white)
VStack(spacing: 12) {
ZStack {
Rectangle()
.frame(width: UIScreen.main.bounds.width-47, height: 35, alignment: .center)
.foregroundColor(Color(UIColor.systemGray6))
.cornerRadius(cornerRadius, corners: [.topLeft, .topRight])
Text("Select Background")
.foregroundColor(Color(UIColor.label))
.font(.subheadline)
.bold()
}
Picker(selection: $select, label: Text("Select Background")) {
Text("Url").tag(0)
Text("Select Image").tag(1)
Text("Gradient").tag(2)
}.pickerStyle(SegmentedPickerStyle())
.padding(EdgeInsets(top: 0, leading: 30, bottom: 0, trailing: 30))
Spacer()
.frame(height: 3)
}
}
if self.select == 0 {
VStack{
ZStack {
RoundedRectangle(cornerRadius: cornerRadius)
.frame(width: UIScreen.main.bounds.width-40, height: 42, alignment: .center)
.foregroundColor(Color(UIColor.systemBackground))
TextField("http://", text: $url)
.padding(10)
.frame(width: UIScreen.main.bounds.width-40)
.foregroundColor(Color(UIColor.label))
.cornerRadius(cornerRadius)
.padding(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 10))
}
Button(action: {
}, label: {
Text("Submit")
.foregroundColor(Color(UIColor.systemBackground))
.bold()
})
.padding(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20))
.foregroundColor(.white)
.font(.subheadline)
.background(Color.blue)
.cornerRadius(cornerRadius)
}
}
if self.select == 1 {
VStack {
Button(action: {
withAnimation {
self.isShowPicker.toggle()
}
}) {
Image(systemName: "photo")
.font(.headline)
.foregroundColor(colorScheme == .dark ? .white : .black)
Text("Import")
.font(.headline)
.foregroundColor(colorScheme == .dark ? .white : .black)
}
.foregroundColor(.black)
}
.sheet(isPresented: $isShowPicker) {
ImagePicker(image: self.$image)
}
}
if self.select == 2 {
VStack(alignment: .trailing){
Button(action: {
bgColors.append(Color.clear)
}) {
Image(systemName: "plus")
.font(.headline)
.foregroundColor(colorScheme == .dark ? .white : .black)
.padding(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 15))
}
List {
ForEach(Array(bgColors.enumerated()), id: \.offset) { index, element in
ZStack {
ColorPicker("Set the background color", selection: $bgColors[index])
}
.padding(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 10))
} .onDelete(perform: delete)
}.background(Color.blue)
}
}
Spacer()
}
.padding(.top, 25)
.ignoresSafeArea(.keyboard)
.background(Color(UIColor.systemGray6))
.edgesIgnoringSafeArea(.all)
}
func delete(at offsets: IndexSet) {
bgColors.remove(atOffsets: offsets)
}
}
struct RoundedCorner: Shape {
var radius: CGFloat = .infinity
var corners: UIRectCorner = .allCorners
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
return Path(path.cgPath)
}
}
extension View {
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
clipShape( RoundedCorner(radius: radius, corners: corners) )
}
}
// extension for keyboard to dismiss
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
struct ImagePicker: UIViewControllerRepresentable {
#Environment(\.presentationMode)
var presentationMode
#Binding var image: Image?
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
#Binding var presentationMode: PresentationMode
#Binding var image: Image?
init(presentationMode: Binding<PresentationMode>, image: Binding<Image?>) {
_presentationMode = presentationMode
_image = image
}
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
image = Image(uiImage: uiImage)
presentationMode.dismiss()
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
presentationMode.dismiss()
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(presentationMode: presentationMode, image: $image)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController,
context: UIViewControllerRepresentableContext<ImagePicker>) {
}
}
The problem is that in your List, the id you give it is \.offset. However, since you are removing data from bgColors, so this data can change. Instead, you should set the id as \.element because it will be constant for each color.
Consider this simplified example, which crashes when you remove a Color from the list:
struct ContentView: View {
#State private var arr: [Color] = [.red, .green, .blue]
var body: some View {
List {
ForEach(Array(arr.enumerated()), id: \.offset) { (index, _) in
ColorPicker("Color", selection: $arr[index])
}
.onDelete(perform: delete)
}
}
private func delete(at offsets: IndexSet) {
arr.remove(atOffsets: offsets)
}
}
And the working example, where the changes are the id given to the List, and the new Binding to the color (note the custom Binding for the selection):
struct ContentView: View {
#State private var arr: [Color] = [.red, .green, .blue]
var body: some View {
List {
ForEach(Array(arr.enumerated()), id: \.element) { (index, _) in
ColorPicker(
"Color",
selection: Binding<Color>(
get: { arr[index] },
set: { arr[index] = $0 }
)
)
}
.onDelete(perform: delete)
}
}
private func delete(at offsets: IndexSet) {
arr.remove(atOffsets: offsets)
}
}

After appending element to array it should show more elements

This is how it looks before and after..
I have a ForEach Loop, and when I click on the card, it appends a new element to the array and then IT SHOULD SHOW 2 CARDS.
But there is still one card. (but I appended the new card element)
So what should I do?
struct TimelineFromUserView: View {
var card: [Card] = cardData
var body: some View {
VStack {
HStack {
Text("History").bold()
.font(.largeTitle)
.padding(.top, 20)
.padding(.leading, 20)
Spacer()
}
Divider()
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 30) {
ForEach(card) { item in
CardForTimeline(card: item)
.onTapGesture {
print("juhuu")
cardData.append(Card(number: 2, start: "01.10.2021", end: "20.10.2021", days: 19, success: false))
print(cardData)
}
.padding()
}
}
.padding(.top, 10)
}
}
}
}
struct Card: Identifiable{
var id = UUID()
var number: Int
var start: String
var end: String
var days: Int
var success: Bool
}
var cardData: [Card] = [
Card(number: 1 ,start: "05.06.2020", end: "15.06.2020", days: 10, success: true)
]
You only pass a copy card of the array cardData into the ForEach but if I understand your code snippets correctly it should be the original array cardData.
If that's not the problem it would be helpful to post more of your code.
I just replace CardForTimeline on Text (because I didn't have your CardForTimeline) and it return me 2 (also I replaced print(cardData) on print(cardData.count)). You can create fully new project and add this code. It will print 2, 3, 4, 5 after taps
struct ContentView: View {
var card: [Card] = cardData
var body: some View {
VStack {
HStack {
Text("History").bold()
.font(.largeTitle)
.padding(.top, 20)
.padding(.leading, 20)
Spacer()
}
Divider()
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 30) {
ForEach(card) { item in
Text(verbatim: "we")
.onTapGesture {
print("juhuu")
cardData.append(Card(number: 2, start: "01.10.2021", end: "20.10.2021", days: 19, success: false))
print(cardData.count)
}
.padding()
}
}
.padding(.top, 10)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct Card: Identifiable{
var id = UUID()
var number: Int
var start: String
var end: String
var days: Int
var success: Bool
}
var cardData: [Card] = [
Card(number: 1 ,start: "05.06.2020", end: "15.06.2020", days: 10, success: true)
]
the problem is that SwiftUI does not know to update the view. Try writing
#State private var card: [Card] = [
Card(number: 1 ,start: "05.06.2020", end: "15.06.2020", days: 10, success: true)
]
instead of: var card: [Card] = cardData.
This should work because then you are telling SwiftUI that it should update the view whenever the value changes.
Additionally you would have to change the adding part of your code to this:
.onTapGesture {
self.card.append(Card(number: 2, start: "01.10.2021", end: "20.10.2021", days: 19, success: false))
}
The above is an approach using a local #State property to manage the state. You could also use a observable object with an array of cards instead. This could look like this:
struct ContentView: View {
#ObservedObject private var cardData = CardData()
var body: some View {
VStack {
HStack {
Text("History").bold()
.font(.largeTitle)
.padding(.top, 20)
.padding(.leading, 20)
Spacer()
}
Divider()
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 30) {
ForEach(cardData.cards) { card in
Text(card.id.uuidString)
.onTapGesture {
self.cardData.add(card: Card(number: 2, start: "01.10.2021", end: "20.10.2021", days: 19, success: false))
}
.padding()
}
}
.padding(.top, 10)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct Card: Identifiable{
var id = UUID()
var number: Int
var start: String
var end: String
var days: Int
var success: Bool
}
class CardData: ObservableObject {
// store the cards and make sure its get-only from the outside
#Published private(set) var cards = [
Card(number: 1 ,start: "05.06.2020", end: "15.06.2020", days: 10, success: true)
]
// MARK: - Intents for modifing the cards, add additional if needed
func add(card: Card) {
self.cards.append(card)
}
}
I hope this will help you with your problems

Is it possible to make an if statement if a list item is equal to a certain number in swift?

I've been working on a truth or dare app, in which you spin a wheel and you randomly get a truth or dare, and I want to have a bunch of random truth questions and dares saved in a database.
I have it set up so when you press the spin button it randomly chooses a truth or dare out of an array.
i've tried it a little and couldn't seem to get it to work. so my question is would it be possible to do something like,
if self.number[0] == 0 || 2 || 3{ (this is where it would get the random truth)}
else{(this is where it would get the random dare)}
I don't know if it will help, but this is the rest of my code.
import SwiftUI
import CoreHaptics
class Colors{
static var drinkGradient = [LinearGradient(gradient: Gradient(colors: [Color("drinkcard"), Color("drinkcard2"), Color("drinkcard")]), startPoint: .top, endPoint: .bottom)]
static var truthGradient = [LinearGradient(gradient: Gradient(colors: [Color("truthcard"), Color("truthcard"), Color("truthcard")]), startPoint: .top, endPoint: .bottom)]
static var dareGradient = [LinearGradient(gradient: Gradient(colors: [Color("darecard"), Color("darecard"), Color("darecard")]), startPoint: .top, endPoint: .bottom)]
}
struct ContentView: View {
#State private var timer:Timer!
#State private var text = [String("Truth"), String("Dare"), String("Truth"), String("Dare"), String("Truth"), String("Dare"), String("Drink!!")]
#State private var foregrounds = [Color.white, Color.white, Color.white, Color.white, Color.white, Color.white, Color.black]
#State private var number = [0]
#State private var timeKeeper: Float = 0
#State private var angle: Double = 0
let generator = UINotificationFeedbackGenerator()
#State var backgrounds = [Colors.truthGradient[0], Colors.dareGradient[0], Colors.truthGradient[0], Colors.dareGradient[0], Colors.truthGradient[0], Colors.dareGradient[0], Colors.drinkGradient[0]]
var body: some View {
ZStack{
Rectangle()
.foregroundColor(Color("background"))
.edgesIgnoringSafeArea(.all)
VStack{
HStack {
Text("Truth, Dare or Drink")
.shadow(radius: 10)
.padding(.top, 20)
.foregroundColor(.white)
.font(.system(.largeTitle, design: .rounded) )
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 100)
.background(Color("banner"))
.edgesIgnoringSafeArea(.top)
.shadow(radius: 15)
}
Text("This is where the Truth or dare will go.")
.frame(width: 350, height: 200)
.foregroundColor(.white)
.font(.system(.headline, design: .rounded))
Spacer()
Text(text[number[0]])
.font(.system(.largeTitle, design: .rounded))
.foregroundColor(foregrounds[number[0]])
.frame(width: 250, height: 250)
.background(backgrounds[number[0]])
.clipShape(RoundedRectangle(cornerRadius: 15, style: .continuous))
.shadow(radius: 10)
.rotationEffect(.degrees(angle))
.animation(.easeIn)
Spacer()
Button(action: {
self.timer = Timer.scheduledTimer(withTimeInterval: 0.10, repeats: true, block: {_ in
self.timeKeeper += 0.10
if self.timeKeeper < 3{
if self.number[0] == self.text.count - 1{
self.number[0] = 0
self.angle += 360
let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
impactHeavy.impactOccurred()
}
else{
self.number[0] += 1
self.angle += 360
let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
impactHeavy.impactOccurred()
}
}
else{
self.number[0] = Int.random(in:
0...self.text.count - 1)
let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
impactHeavy.impactOccurred()
self.angle += 360
self.timeKeeper = 0
self.timer.invalidate()
}
})
}) {
Text("Spin")
.font(.system(.title, design: .rounded))
.foregroundColor(.white)
.frame(width: 250, height: 50)
.background(Color("button"))
.clipShape(RoundedRectangle(cornerRadius: 15, style: .continuous))
.shadow(radius: 15)
}
Spacer()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
if (self.number[0] == 0 || self.number[0] == 2 || self.number[0] == 3) {
print("truth")
} else {
print("dare")
}

BMI Calculator always returns 0 in swift

I've been trying to learn swift and decided to build a little BMI calculator just to get used to everything. I thought I had everything about figured out, but for some reason it just returns 0 every time. I can't quite understand why exactly. I feel like it might be something to do with the #State var s but, I'm not sure cause the ones that aren't for the textinputs seem to work just find.
This is what I've got:
import SwiftUI
struct ContentView: View {
#State var heightFeet: String = ""
#State var heightInches: String = ""
#State var weight: String = ""
#State var bmi: String = ""
#State var bmiText: String = ""
#State var bmiDetail: String = ""
var body: some View{
VStack {
VStack {
VStack {
Text("BMI Calculator")
.font(.largeTitle)
.padding(.top)
.foregroundColor(Color.white)
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 100)
.background(Color.black)
.shadow(radius: 10)
VStack {
TextField("Enter height in feet", text: $heightFeet)
.padding(.all)
.frame(width: 300)
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(Color.clear)
.foregroundColor(.black)
.shadow(radius: 5)
.keyboardType(.numberPad)
TextField("Enter height in inches", text: $heightInches)
.padding(.all)
.frame(width: 300)
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(Color.clear)
.foregroundColor(.black)
.shadow(radius: 5)
.keyboardType(.numberPad)
TextField("Enter weight", text: $weight)
.padding(.all)
.frame(width: 300)
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(Color.clear)
.foregroundColor(.black)
.shadow(radius: 5)
.keyboardType(.numberPad)
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color.gray)
VStack {
Text(bmiText)
.font(.title)
.foregroundColor(Color.black)
Text(bmi)
.font(.largeTitle)
.foregroundColor(Color.black)
Text(bmiDetail)
.padding(.all)
.font(.headline)
.foregroundColor(.black)
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.padding()
}
HStack {
Button(action: {
let heightInFeet = Int(self.heightFeet)
let heightInInches = Int(self.heightInches)
let weights = Int(self.weight)
let height = Int(heightInFeet! * 12 + heightInInches!)
let bmis = Int(weights! / (height * height) * 703)
self.bmi = "\(bmis)%"
self.bmiText = "Your BMI is"
if bmis > 30{
self.bmiDetail = "You are considered to be obese."
}
else if bmis > 25{
self.bmiDetail = "You are considered to be overweight."
}
else{
self.bmiDetail = "You are healthy!"
}
}) {
HStack {
Image(systemName: "plus.square")
Text("Calculate")
}
.frame(minWidth: 0, maxWidth: .infinity)
.font(.title)
.padding(.horizontal, 60.0)
.padding(.vertical, 20)
.background(Color("button"))
.cornerRadius(10)
.foregroundColor(Color.black)
.shadow(radius: 5)
.padding(.vertical, 40)
.padding(.horizontal, 20)
}
}
.background(Color.gray)
}
.frame(minWidth:0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color.gray)
.edgesIgnoringSafeArea(.all)
.onTapGesture {
self.endTextEditing()
}
}
}
extension View {
func endTextEditing() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),
to: nil, from: nil, for: nil)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The problem is not the code, but your calculation.
let bmis = Int(weights! / (height * height) * 703)
Example:
weights = 100 kg
height = 76 inch
result ==> 100 / (76 * 76) * 703 ==> 100 / 5776 * 703 ==> 100 / 4060528 ==> ~0
You can change the formula to
703 * weight / height * height
to get the correct result.
For the formula see www.cdc.gov

SwiftUI: How to cancel timer in SwiftUI view?

I am using a timer in a SwiftUI view as in code below. It works as expected BUT under some conditions I want to cancel/stop that timer. There seems to be no ".cancel" property or method on the timer var. How do I cancel this timer? Any ideas/tips?
import SwiftUI
struct ContentView: View {
#State private var selection = 2
#State private var rotation: Double = GBstrtest
let timer = Timer.publish (every: 0.8, on: .current, in: .common).autoconnect()
var body: some View {
TabView(selection: $selection){
Text("Settings")
.font(.title)
.tabItem {
VStack {
Image(systemName: "gear")
.font(Font.system(.title ))
Text("Settings")
}
}
.tag(0)
VStack {
Divider().padding(2)
ZStack {
Image("image1")
.resizable()
.aspectRatio(contentMode: .fit)
Image("image2")
.resizable()
.aspectRatio(contentMode:.fit)
.rotationEffect(.degrees(rotation))
.animation(.easeInOut(duration: 0.3) )
.padding(EdgeInsets(top: 0, leading: 50, bottom: 0, trailing: 50))
}
Spacer()
}
.tabItem {
VStack {
Image(systemName: "speedometer")
.font(Font.system(.title ))
Text("Read Meter")
}
}
.tag(1)
}
.onReceive(timer) {
_ in self.rotation = Double.random(in: 0 ... 200)
// How do I cancel timer HERE!?
}
}
}
Inside your conditional statement, use the following code:
self.timer.upstream.connect().cancel()
Full cycle goes like this:
struct MySwiftUIView : View {
...
#State var timer = Timer.publish (every: 1, on: .current, in: .common).autoconnect()
#State var timeRemaining = -1
var body: some View {
ZStack {
// Underlying shapes etc as needed
Text("\(timeRemaining)").
.opacity(timeRemaining > 0 ? 1 : 0)
.onReceive(timer) { _ in
if self.timeRemaining < 0 {
// We don't need it when we start off
self.timer.upstream.connect().cancel()
return
}
if self.timeRemaining > 0 {
self.timeRemaining -= 1
} else {
self.timer.upstream.connect().cancel()
// Do the action on end of timer. Text would have been hidden by now
}
}
.onTapGesture { // Or any other event handler that should start countdown
self.timeRemaining = Int(delayValue)
self.timer = Timer.publish (every: 1, on: .current, in:
.common).autoconnect()
}
}
Voila! A reusable timer, use it as many times as you'd like!

Resources