UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Array compare values

Forums > Swift

Hello, i'm trying to subtract a value with the next value inside an array and append the difference into another array, i can't figure out how to do. With this code i get this error "Type '()' cannot conform to 'View'". Thank you for the help!

var numbers: [Int] = [1,4,7,9,11,16,18,25]

var differenceArray: [Int] = []  //should be [-3, -3, -2, -2, -5, -2, -7]

ForEach(numbers.indices) { i in
    let actualNumber = numbers[i]
    let nextNumber = numbers[i + 1]
    let difference = actualNumber - nextNumber
    differenceArray.append(difference)

3      

@twostraws  Site AdminHWS+

Swift thinks you're trying to do your calculations as part of your SwiftUI view rendering, which isn't what you mean at all. It's better to pull that logic out into a computed property (for example), and leave your SwiftUI body just rendering view code.

4      

Hi Gas9220

The other thing is you will need to watch out for Index out of range error

var numbers = [1, 4, 7, 9, 11, 16, 18, 25]

var differenceArray: [Int] {
    var computedNumbers = [Int]()
    for i in 0..<numbers.count - 1 { // `numbers.count - 1` to avoid "Fatal error: Index out of range"
        let result = numbers[i] - numbers[i + 1]
        computedNumbers.append(result)
    }
    return computedNumbers
}

Result

[-3, -3, -2, -2, -5, -2, -7]

For your information ForEach is a SwiftUI View and will not work in Swift(UIKit)

4      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

hi,

sorry if i get a little nerdy on this, but ideally what you want to do is take the numbers array without the last entry and "coordinate-wise" subtract the numbers array without the first entry. so you would want to form a hypothetical minus operation:

  • [1,4,7,9,11,16,18] minus [4,7,9,11,16,18,25] equals [-3,-3,-2,-2,-5,-2,-7]

the following functional Swift code does this using zip to pair the two array slices together, and then applying a mapping function that performs the subtraction of items:

let numbers = [1, 4, 7, 9, 11, 16, 18, 25]
let diffs = zip(numbers.dropLast(), numbers.dropFirst()).map({ $0 - $1 })
print(diffs)

hope that might be of interest to some ...

DMG

6      

Hi DMG,

Thought there was a way to use .map 🤔. Like it 👍

3      

Thanks for your reply, are very useful. I have another problem. I'm trying to sort, in a list, where each row contains the number and the difference from the previous value. The screenshot shows the result I am trying to achieve. https://ibb.co/CBQBnmk

Do you think this is a good solution?

import SwiftUI

struct ContentView: View {
    var numbers: [Double] = [1, 4, 7, 9, 11, 16, 18, 25, 20]

    var differenceArray: [Double] {
        var computedNumbers = [Double]()
        for i in 0..<numbers.count {
            if i != 0 {
                let result = numbers[i] - numbers[i - 1]
                computedNumbers.append(result)
            } else {
                computedNumbers.append(0)
            }
        }
        return computedNumbers
    }

    var body: some View {
        List(numbers.indices) { index in
            HStack {
                Text(String(numbers[index]))
                Spacer()
                if differenceArray[index] == 0 {
                Text("-")
                } else if differenceArray[index] > 0 {
                    Text(String("+ " + "\(differenceArray[index])"))
                } else {
                    Text(String((differenceArray[index])))
                }
            }
        }
    }
}

3      

hi,

now that i see where you want to go with this, i put together something ... but then i was confused by when you said "I'm trying to sort ..."

so let me show you what i put together, which is in essence, not much different than what you have (except that i don't bother to compute all the differences, but just those that need to be displayed).

var numbers: [Double] = [1, 4, 7, 9, 11, 16, 18, 25, 20]
var body: some View {
    List(numbers.indices) { index in
        HStack {
            Text(String(numbers[index]))
            Spacer()
            Text(differenceString(index: index))
        }
    }
}

func differenceString(index: Int) -> String {
    guard index > 0 else { return "-" }
    let difference = numbers[index] - numbers[index - 1]
    return difference > 0 ? "+\(difference)" : "\(difference)"
}

hope that moves the conversation forward ...

DMG

4      

I am Italian, I am trying to explain in English my problem as best I can. With all your suggestion, i've tryed in different ways but i'm not able to solve. The code above was a simplified example of my real code, because i was thinking to find a solution and then adapt to my code but this don't happen. So, at this point, i think is better to share my project code. What I am trying to achieve is to have for each weight misuration the difference with the previos value. Actualy i'am able to add and delete misurations, and are sorted by date. https://youtu.be/ItTDJiewmJQ

Thank you for your patience. 🙏

Pet+CoreDataProperties

extension Pet {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Pet> {
        return NSFetchRequest<Pet>(entityName: "Pet")
    }

    @NSManaged public var allergies: String?
    @NSManaged public var breed: String?
    @NSManaged public var dateOfBirth: Date?
    @NSManaged public var gender: String?
    @NSManaged public var image: Data?
    @NSManaged public var name: String?
    @NSManaged public var reproductiveState: String?
    @NSManaged public var type: String?
    @NSManaged public var weight: NSSet?

    var petName: String {
        name ?? "No name"
    }

    var petType: String {
        type ?? "No type"
    }

    var petGender: String {
        gender ?? "No gender"
    }

    var petDateOfBirth: Date {
        dateOfBirth ?? Date()
    }

    var petBreed: String {
        breed ?? "No breed"
    }

    var petReproductiveState: String {
        reproductiveState ?? "Not specified"
    }

    var petAge: String {
        calculateAge(for: petDateOfBirth)
    }

    var petImg : UIImage {
        let petImg = UIImage(data: image ?? Data())
        return petImg ?? #imageLiteral(resourceName: "dog")
    }

    var petAllegies: String {
        allergies ?? "No allergies"
    }

    public var petWeightMisurations: [Weight] {
        let set = weight as? Set<Weight> ?? []

        return set.sorted {
            $0.petWeightDate > $1.petWeightDate
        }
    }
}

// MARK: Generated accessors for weight
extension Pet {

    @objc(addWeightObject:)
    @NSManaged public func addToWeight(_ value: Weight)

    @objc(removeWeightObject:)
    @NSManaged public func removeFromWeight(_ value: Weight)

    @objc(addWeight:)
    @NSManaged public func addToWeight(_ values: NSSet)

    @objc(removeWeight:)
    @NSManaged public func removeFromWeight(_ values: NSSet)

}

extension Pet : Identifiable {

}

Weight+CoreDataProperties

extension Weight {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Weight> {
        return NSFetchRequest<Weight>(entityName: "Weight")
    }

    @NSManaged public var date: Date?
    @NSManaged public var notes: String?
    @NSManaged public var weight: Double
    @NSManaged public var pet: Pet?

    var petWeight: Double {
        weight
    }

    var petWeightDate: Date {
        date ?? Date()
    }

    var petWeightNotes: String {
        notes ?? ""
    }

}

extension Weight : Identifiable {

}

WeightHistoryScreen

import SwiftUI

struct WeightHistoryScreen: View {
    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(entity: Weight.entity(), sortDescriptors: []) var weightMisuration: FetchedResults<Weight>

    @Binding var isShow: Bool
    @State var isAddPresented: Bool = false
    @ObservedObject var pet: Pet

    var body: some View {
        NavigationView {
            ZStack {
                if pet.petWeightMisurations.isEmpty {
                    VStack {
                        Text("No misurations")
                            .font(.title2)
                            .bold()
                            .foregroundColor(.gray)
                    }
                    .zIndex(1)
                }

                List {
                    ForEach(pet.petWeightMisurations, id: \.self) { misuration in
                        VStack(alignment: .leading) {
                            HStack {
                                Text(String(misuration.petWeight) + "Kg")
                                Text(stringFromDate(date: misuration.petWeightDate))
                                Spacer()
                                // difference here
                            }
                            if misuration.petWeightNotes != "" {
                                Text(misuration.petWeightNotes)
                            }
                        }
                    }
                    .onDelete(perform: { offsets in
                        deleteMisuration(at: offsets, from: pet)
                    })
                }

                if isAddPresented {
                    AddWeightView(isAddWeightPresented: $isAddPresented, pet: pet)
                        .zIndex(2)
                }
            }
            .navigationBarTitle("Weight Misurations", displayMode: .inline)
            .navigationBarItems(trailing:
                                    Button(action: {
                                        withAnimation(.easeIn) {
                                            isAddPresented = true
                                        }
                                    }, label: {
                                        Image(systemName: "plus.circle.fill")
                                            .font(.title)
                                            .foregroundColor(.orange)
                                    })
            )
        }
    }

    func deleteMisuration(at offsets: IndexSet, from pet: Pet) {
        for offset in offsets.sorted().reversed() {
            let misurationToDelete = pet.petWeightMisurations[offset]
            pet.removeFromWeight(misurationToDelete)
        }
        if viewContext.hasChanges {
            try? self.viewContext.save()
        }
    }
}

struct WeightHistoryScreen_Previews: PreviewProvider {
    static var previews: some View {
        WeightHistoryScreen(isShow: .constant(true), pet: Pet.example[0])
    }
}

3      

hi,

i see your problem ... you construct the List using this construct

ForEach(pet.petWeightMisurations, id: \.self) { misuration in ...

and so on each misuration (a weight, date, and notes) you want to display these values, but then get stuck on figuring out the difference from the pevious misuration because you don't have its index.

i see perhaps two options to consider.

(1) rewrite the ForEach to work with indices and use those indices to reference the values you display, e.g.,

ForEach(pet.petWeightMisurations.indices, id: \.self) { index in ...
    // most of what you have now, except use the index, such as
    Text(String(pet.petWeightMisurations[index].petWeight) + "Kg")
    //
}

you should then be able to compute that difference as suggested earlier using the pet.petWeightMisurations array according to this index.

the downside of using indexing, for me, is that the syntax becomes a little cumbersome (i think, anyway).

(2) instead of structuring the ForEach around either the array of pet.petWeightMisurations or its indexing, consider creating an array of structs which have exactly the data you want to display on each row of the table. perhaps something like this:

struct WeightMisuration: Hashable {
    let date: Date
    let notes: String
    let weight: Double
    var differenceDisplay: String
  // you'll see below why this mutating function is here ...
  mutating func updateDifferenceDisplay(newValue: String) {
    differenceDisplay = newValue
    }
}

then the ForEach gets very simple:

List {
    ForEach(weightMisurations, id: \.self) { weightMisuration in
        VStack(alignment: .leading) {
            HStack {
                Text(String(weightMisuration.weight) + "Kg")
                Text(stringFromDate(date: weightMisuration.date))
                Spacer()
                Text(weightMisuration.differenceDisplay)
            }
            if weightMisuration.notes != "" {
                Text(weightMisuration.notes)
            }
        }
    }
    .onDelete(perform: { offsets in
        deleteMisuration(at: offsets, from: pet)
    })
}

now you need a computed variable weightMisurations for the View which produces such an array on demand. something like this would work

var weightMisurations: [WeightMisuration] {
  // convert the pet.petWeightMisurations array into an array of WeightMisuration
  // with a default differenceDisplayString of "-"
    var misurations = pet.petWeightMisurations.map({
        WeightMisuration(date: $0.petWeightDate, notes: $0.petWeightNotes, weight: $0.petWeight, differenceDisplay: "-")
    })
  // then go through the misurations array, computing the difference strings as before,
  // but using a mutating function defined on WeightMisuration
    for index in misurations.indices {
        if index > 0 {
            let difference = misurations[index] - misurations[index - 1]
            let differenceDisplay = difference > 0 ? "+\(difference)" : "\(difference)"
            misurations[index].updateDifferenceDisplay(newValue: differenceDisplay)
        }
    }
}

the nice thing about this idea is that it takes all the computation out of the view code; and should you add or delete any measurement and the view is redrawn, the weightMisurations array will be recomputed with the new set of differences.

hope that helps,

DMG

3      

++  var previousWeight:Double = 0.0 
ForEach(pet.petWeightMisurations, id: \.self) { misuration in
                        VStack(alignment: .leading) {
                            HStack {
                                Text(String(misuration.petWeight) + "Kg")
                                Text(stringFromDate(date: misuration.petWeightDate))
                                Spacer()
++                                var difference = misuration.petWeight - previousWeight 
++                               previousWeight = misuration.petWeight 
                            }
                            if misuration.petWeightNotes != "" {
                                Text(misuration.petWeightNotes)
                            }
                        }

Misurations is Measurement in English, btw.

3      

Thank you to delawaremathguyr, my problem is solved.

3      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.