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
Related
I'm trying to check if my appearedNames array already contains my currentPerson string but unfortunately i got this error: Cannot convert value of type 'String?' to expected argument type '(ColoredName) throws -> Bool'... how should i use .contains with a struct?
struct ColoredName: Identifiable {
let id = UUID()
var name: String
var color: Color
}
struct ContentView: View {
#State private var names = ["Steve", "Bill", "Jeff", "Elon"]
#State private var appearedNames = [ColoredName]()
#State private var currentPerson: String?
var body: some View {
NavigationView {
VStack {
NavigationLink("Change view") {
SecondView(appearedNames: $appearedNames)
}
HStack {
Button {
if currentPerson != nil && !appearedNames.contains(currentPerson) // <-- I got the error here {
appearedNames.append(ColoredName(name: currentPerson ?? "", color: .red))
}
changePerson()
} label: {
Text(currentPerson ?? "Start")
.foregroundColor(currentPerson != nil ? .red : .black)
.font(.largeTitle)
}
Button {
if currentPerson != nil {
appearedNames.append(ColoredName(name: currentPerson ?? "", color: .green))
}
changePerson()
} label: {
Text(currentPerson ?? "Start")
.foregroundColor(currentPerson != nil ? .green : .black)
.font(.largeTitle)
}
}
}
}
}
func changePerson() {
if names.count > 0 {
let index = Int.random(in: 0..<names.count)
currentPerson = names[index]
names.remove(at: index)
}
}
}
struct SecondView: View {
#Binding var appearedNames: [ColoredName]
var body: some View {
VStack {
ForEach(appearedNames) { person in
Text(person.name)
.foregroundColor(person.color)
.font(.largeTitle)
.bold()
}
}
}
}
try something like this, where you compare the name of the ColoredName in the array, with the String currentPerson (unwrapped):
if currentPerson != nil && !appearedNames.contains(where: { $0.name == currentPerson!} ) {
appearedNames.append(ColoredName(name: currentPerson ?? "", color: .red))
}
or, alternatively:
if let theName = currentPerson, !appearedNames.contains(where: { $0.name == theName} ) {
appearedNames.append(ColoredName(name: theName, color: .red))
}
How can I make a Form in which the elements are automatically divided into sections based on their first letter and add to the right the alphabet jumper to show the elements starting by the selected letter (just like the Contacts app)?
I also noted a strange thing that I have no idea how to recreate: not all letters are shown, some of them appear as "•". However, when you tap on them, they take you to the corresponding letter anyway. I tried using a ScrollView(.vertical) inside a ZStack and adding .scrollTo(selection) into the action of the Buttons, however 1) It didn't scroll to the selection I wanted 2) When I tapped on the "•", it was as if I was tapping on all of them because they all did the tapping animation 3) I wasn't able to divide the List as I wanted to.
I have this:
import SwiftUI
struct ContentView: View {
let alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W", "X","Y", "Z"]
let values = ["Avalue", "Bvalue", "Cvalue", "Dvalue"]
var body: some View {
ScrollViewReader{ scrollviewr in
ZStack {
ScrollView(.vertical) {
VStack {
ForEach(alphabet, id: \.self) { letters in
Button(letters){
withAnimation {
scrollviewr.scrollTo(letters)
}
}
}
}
}.offset(x: 180, y: 120)
VStack {
ForEach(values, id: \.self){ vals in
Text(vals).id(vals)
}
}
}
}
}
}
But I'd want it like this:
import SwiftUI
struct AlphabetSort2: View {
let alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W", "X","Y", "Z"]
let values = ["Avalue", "Bvalue", "Cvalue", "Dvalue", "Mvalue", "Zvalue"]
var body: some View {
ScrollView {
ScrollViewReader { value in
ZStack{
List{
ForEach(alphabet, id: \.self) { letter in
Section(header: Text(letter)) {
ForEach(values.filter { $0.hasPrefix(letter) }, id: \.self) { vals in
Text(vals).id(vals)
}
}.id(letter)
}
}
HStack{
Spacer()
VStack {
ForEach(0..<alphabet.count, id: \.self) { idx in
Button(action: {
withAnimation {
value.scrollTo(alphabet[idx])
}
}, label: {
Text(idx % 2 == 0 ? alphabet[idx] : "\u{2022}")
})
}
}
}
}
}
}
}
}
struct AlphabetSort2_Previews: PreviewProvider {
static var previews: some View {
AlphabetSort2()
}
}
Add an swipe-up-and-down action for the alphabet jumper, so we can get an UILocalizedIndexCollation -like swipe effect, it works for view and add mode, but delete, I guess it is due to SwiftUI's UI refresh mechanism.
extension String {
static var alphabeta: [String] {
var chars = [String]()
for char in "abcdefghijklmnopqrstuvwxyz#".uppercased() {
chars.append(String(char))
}
return chars
}
}
struct Document: View {
#State var items = ["Alpha", "Ash", "Aman", "Alisia", "Beta", "Baum", "Bob", "Bike", "Beeber", "Beff", "Calipha", "Cask", "Calf", "Deamon", "Deaf", "Dog", "Silk", "Seal", "Tiger", "Tom", "Tan", "Tint", "Urshinabi", "Verizon", "Viber", "Vein", "Wallet", "Warren", "Webber", "Waiter", "Xeon", "Young", "Yoda", "Yoga", "Yoger", "Yellow", "Zeta"]
var body: some View {
ScrollViewReader { scrollView in
HStack {
List {
ForEach(String.alphabeta, id: \.self){ alpha in
let subItems = items.filter({$0.starts(with: alpha)})
if !subItems.isEmpty {
Section(header: Text(alpha)) {
ForEach(subItems, id: \.self) { item in
Text(item)
}.onDelete(perform: { offsets in
items.remove(at: offsets.first!)
})
}.id(alpha)
}
}
}
VStack{
VStack {
SectionIndexTitles(proxy: scrollView, titles: retrieveSectionTitles()).font(.footnote)
}
.padding(.trailing, 10)
}
}
}
// .navigationBarTitleDisplayMode(.inline)
// .navigationBarHidden(true)
}
func retrieveSectionTitles() ->[String] {
var titles = [String]()
titles.append("#")
for item in self.items {
if !item.starts(with: titles.last!){
titles.append(String(item.first!))
}
}
titles.remove(at: 0)
if titles.count>1 && titles.first! == "#" {
titles.append("#")
titles.removeFirst(1)
}
return titles
}
}
struct Document_Previews: PreviewProvider {
static var previews: some View {
Document()
}
}
struct SectionIndexTitles: View {
class IndexTitleState: ObservableObject {
var currentTitleIndex = 0
var titleSize: CGSize = .zero
}
let proxy: ScrollViewProxy
let titles: [String]
#GestureState private var dragLocation: CGPoint = .zero
#StateObject var indexState = IndexTitleState()
var body: some View {
VStack {
ForEach(titles, id: \.self) { title in
Text(title)
.foregroundColor(.blue)
.modifier(SizeModifier())
.onPreferenceChange(SizePreferenceKey.self) {
self.indexState.titleSize = $0
}
.onTapGesture {
proxy.scrollTo(title, anchor: .top)
}
}
}
.gesture(
DragGesture(minimumDistance: indexState.titleSize.height, coordinateSpace: .named(titles.first))
.updating($dragLocation) { value, state, _ in
state = value.location
scrollTo(location: state)
}
)
}
private func scrollTo(location: CGPoint){
if self.indexState.titleSize.height > 0{
let index = Int(location.y / self.indexState.titleSize.height)
if index >= 0 && index < titles.count {
if indexState.currentTitleIndex != index {
indexState.currentTitleIndex = index
print(titles[index])
DispatchQueue.main.async {
let impactMed = UIImpactFeedbackGenerator(style: .medium)
impactMed.impactOccurred()
// withAnimation {
proxy.scrollTo(titles[indexState.currentTitleIndex], anchor: .top)
// }
}
}
}
}
}
}
struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
struct SizeModifier: ViewModifier {
private var sizeView: some View {
GeometryReader { geometry in
Color.clear.preference(key: SizePreferenceKey.self, value: geometry.size)
}
}
func body(content: Content) -> some View {
content.background(sizeView)
}
}
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)
}
}
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.
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!