How do you initialize values in SwiftUI before view is loaded?

Viewed 4545

I want to take say a stock price and then generate values within .05% in a 2D array.

I am passing the stock information such as stock name and stock price.

But when I try and run a method that creates the 2D array of values I am met with a "Cannot use instance member '' within property initializer; property initializers run before 'self' is available"

I was looking how to get around this.

Say Amazon stock is 2400. This data passes into the new view. What I want to do is create a 2D array of values near 2400, perhaps 2405 and 2395. I want a 2D array of these values generated in the new view. But I am getting an error that I cannot generate the values until the view is loaded, but I need them to generate the view.

I think I have to use init method but I am not sure how.

This is the line that is throwing the error above.

var new_prices = generateValues(price: position.stock_price)

Here is the method

func generateValues(price: Double) -> [[Double]] {
    var values = [[Double]]()
     for x in 1...5 {
        values[0][x-1] = (price + price * 0.005 * Double(x))
    }
    return values
}

This is what is being passed in the stock postion

struct Position : Identifiable {
    var id = UUID()
    var stock_name: String
    var stock_ticker: String
    var stock_price: Double
    var price_paid: Double

}

Arguments passed throwing an error for no argument init().

struct PositionDetail_Previews: PreviewProvider {
    static var previews: some View {
        PositionDetail(position: Position(stock_name: "Amazon", 
stock_ticker: "AMZN", stock_price : 2400, price_paid : 2300))
    }
}
4 Answers

You could do

struct ContentView: View{
@State var new_prices = [[Double()]]
        init () {
            new_prices = generateValues(price: position.stock_price)
        }
    var body: some View{
           your view...
     }
}

If I correctly understood & replicated your code initially you have got this

demo

so here is a solution (tested with Xcode 11.4 / iOS 13.4)

func generateValues(price: Double) -> [[Double]] {
    var values = [[Double]]()
    for x in 1...5 {
        values[0][x-1] = (price + price * 0.005 * Double(x))
    }
    return values
}

struct PositionDetail: View {
    var position: Position
    var new_prices: [[Double]]

    init(position: Position) {
        self.position = position
        self.new_prices = generateValues(price: position.stock_price)
    }

    var body: some View {
        VStack { // << just for demo
            Text("Position: \(self.position.stock_name)")
            Text("First price: \(self.new_prices[0][0])")
        }
    }
}

other code with no changes.

This is how you call dynamic functions to initialize the current version of SwiftUI View data. (Note: no .init() needed):

import SwiftUI

struct Position : Identifiable {
    var id = UUID()
    var stock_name: String
    var stock_ticker: String
    var stock_price: Double
    var price_paid: Double
}

struct PositionDetail: View {
    var position: Position

    func generateValues(price: Double) -> [[Double]] {
        var values: [[Double]] = Array(repeating: Array(repeating: 0, count: 5), count: 1)

        for x in 1...5 {
            values[0][x-1] = (price + price * 0.005 * Double(x))
        }

        return values
    }

    var body: some View {
        let values   = generateValues(price: position.stock_price)

        return HStack {
            ForEach(values, id:\.self) { row in
                HStack {
                    ForEach(row, id: \.self) { value in
                        Text("\(value)")
                    }
                }
            }
        }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}


struct PositionDetail_Previews: PreviewProvider {
    static var previews: some View {
        PositionDetail(position: Position(stock_name: "Amazon", stock_ticker: "AMZN", stock_price : 2400, price_paid : 2300))
    }
}

Note: The generateValues function, as specified, was broken...

Using your position model:

struct Position : Identifiable {
    var id = UUID()
    var stock_name: String
    var stock_ticker: String
    var stock_price: Double
    var price_paid: Double

}

You could generate the values when creating the view, given an existing Position:

struct StockPriceViewBeforehand: View {
    // MARK: Properties
    var position: Position
    var newPrices: [[Double]]

    // MARK: Body
    var body: some View {
        Text("Use prices here")
    }
}

struct StockPriceViewBeforehand_Previews: PreviewProvider {
    static var previews: some View {
        let amznPosition = Position(stock_name: "Amazon", stock_ticker: "AMZN", stock_price : 2400, price_paid : 2300)        
       return StockPriceViewBeforehand(position: amznPosition, newPrices: StocksValueGenerator.generateValues(price: amznPosition.stock_price))
    }
}


final class StocksValueGenerator {
    static func generateValues(price: Double) -> [[Double]] {
        var values = [[Double]]()
        for x in 1...5 {
            values[0][x-1] = (price + price * 0.005 * Double(x))
        }
        return values
    }
}

Note that I used the generateValues method as a static function of a StocksValueGenerator class above, but you could use it as you wish.

As an alternative, you could call the generate values method when the view appears:

struct StockPriceView: View {
    // MARK: Properties
    var position: Position
    @State var values = [[Double]]()

    // MARK: Body
    var body: some View {
        Text("Your view here")
            .onAppear {
                self.values = self.generateValues(price: self.position.stock_price)
            }
    }

    // MARK: Methods
    func generateValues(price: Double) -> [[Double]] {
        var values = [[Double]]()
        for x in 1...5 {
            values[0][x-1] = (price + price * 0.005 * Double(x))
        }
        return values
    }
}

struct StockPriceView_Previews: PreviewProvider {
    static var previews: some View {
        let amznPosition = Position(stock_name: "Amazon", stock_ticker: "AMZN", stock_price : 2400, price_paid : 2300)

        return StockPriceView(position: amznPosition)
    }
}

The difference between these two options is that in the first one you would create the View with the already generated newPrices, so the View could display them accordingly when appearing. On the second option, you would first have an empty array of prices, which would then be assigned the generated ones.

(I know the second option doesn't count as initializing values before the view is loaded, but I just thought I'd give you some options)

Related