SwiftUI - issue with UserDefaults

60 views Asked by At

In SwiftUI I made a view. The input of data for lensFocalLength comes via a slider or a stepper. The idea is that the user changes the value and the next time he opens the view, his last used data is shown.

For this method I used the UserDefaults method. But it is not consistent. When user changes the value to for example 100 mm, the next time the view is opened it shows 98 of 105.

This is the code:

import SwiftUI
import Foundation

struct Calc2View: View {
        @State private var lensFocalLength: Double
        let sensorWidth = 36.0 // Full frame sensor width

        init() {
            _lensFocalLength = State(initialValue: UserDefaults.standard.double(forKey: "selectedLens") != 0 ? UserDefaults.standard.double(forKey: "selectedLens") : 50.0)
        }
    
    var body: some View {
        NavigationView {
            ScrollView {
                VStack {
                    //Color.clear.frame(height: 10)
                    HStack{
                        Text("Camera input")
                            .font(.headline)
                            .fontWeight(.bold)
                        Spacer()
                    }
                    
                    Divider()
                    VStack{
                        HStack {
                            Text("Lens")
                                .font(.headline)
                                .multilineTextAlignment(.leading)
                            Spacer()
                            
                            Text("\(lensFocalLength, specifier: "%.0f") mm")
                            Spacer()
                            
                            Stepper("", value: $lensFocalLength, in: 1...1000)
                        }

                        HStack {
                            Slider(value: $lensFocalLength, in: 10...1000, step: 1)
                                .accentColor(.blue)  
                        }
                    }
                    .onChange(of: lensFocalLength) { newValue, _  in
                        UserDefaults.standard.set(newValue, forKey: "selectedLens")  
                    }
                    
                    if lensFocalLength > 0 {
                        let fov = 2 * atan(sensorWidth / (2 * lensFocalLength)) * (180 / .pi) // New formula
                        let humanFOV = 200.0
                        let fovealFOV = 120.0
                        
                        let nikonSensorWidth = 23.6
                        let canonSensorWidth = 22.3
                        let microFourThirdsSensorWidth = 17.3
                        
                        let nikonFOV = 2 * atan(nikonSensorWidth / (2 * lensFocalLength)) * (180 / .pi)
                        let canonFOV = 2 * atan(canonSensorWidth / (2 * lensFocalLength)) * (180 / .pi)
                        let microFourThirdsFOV = 2 * atan(microFourThirdsSensorWidth / (2 * lensFocalLength)) * (180 / .pi)
                        
                        let nikonEquivalentFocalLength = lensFocalLength / 1.5
                        let canonEquivalentFocalLength = lensFocalLength / 1.6
                        let microFourThirdsEquivalentFocalLength = lensFocalLength / 2.0
                        
                        VStack(alignment: .leading) {
                            Divider()
                            Color.clear.frame(height: 5)
                            VStack(alignment: .leading){
                                Text("Lens Field of View:")
                                    .font(.headline)
                                    .fontWeight(.bold)
                                HStack {
                                    Text("FullFrame")
                                    Spacer()
                                    Text("\(fov, specifier: "%.1f") degrees")
                                }
                                
                                HStack {
                                    Text("APS-C")
                                    Spacer()
                                    Text("\(nikonFOV, specifier: "%.1f") degrees")
                                }
                                
                                HStack {
                                    Text("APS-C Canon")
                                    Spacer()
                                    Text("\(canonFOV, specifier: "%.1f") degrees")
                                }
                                
                                HStack {
                                    Text("Micro 4/3rd")
                                    Spacer()
                                    Text("\(microFourThirdsFOV, specifier: "%.1f") degrees")
                                }
                                Divider()

                                Color.clear.frame(height: 5)
                                Text("Equivalent lens:")
                                    .font(.headline)
                                    .fontWeight(.bold)
                                
                                HStack {
                                    Text("Full Frame")
                                    Spacer()
                                    Text("\(lensFocalLength, specifier: "%.0f") mm")
                                }
                                
                                HStack {
                                    Text("APS-C")
                                    Spacer()
                                    Text("\(nikonEquivalentFocalLength, specifier: "%.0f") mm")
                                }
                                
                                HStack {
                                    Text("APS-C Canon")
                                    Spacer()
                                    Text("\(canonEquivalentFocalLength, specifier: "%.0f") mm")
                                }
                                
                                HStack {
                                    Text("Micro 4/3rd")
                                    Spacer()
                                    Text("\(microFourThirdsEquivalentFocalLength, specifier: "%.0f") mm")
                                }
                                
                                Divider()
                                Color.clear.frame(height: 5)
                                Text("Full Frame Field of View:")
                                    .font(.headline)
                                    .fontWeight(.bold)
                                Color.clear.frame(height: 5)
                                
                            }
                        }
                        //.padding(.bottom)
                        
                        VStack {
                            ZStack {
                                FOVTriangle(fov: humanFOV)
                                    .fill(Color.green.opacity(0.5))
                                FOVTriangle(fov: fovealFOV)
                                    .fill(Color.blue.opacity(0.5))
                                FOVTriangle(fov: fov)
                                    .fill(Color.pink.opacity(1.0))
                            }
                            .frame(height: 200)
                            .frame(width: 200)
                            
                            Image(systemName: "camera") // Add SF Symbol
                                .font(.largeTitle)
                            //.padding(.top, 20)
                            Spacer()
                            
                            HStack{
                                Image(systemName: "square.fill")
                                    .foregroundColor(.green)
                                    .opacity(0.5)
                                    .font(.system(size: 20))
                                Text("FoV both eyes")

                                Spacer()
                                Image(systemName: "square.fill")
                                    .foregroundColor(.red)
                                    .opacity(1.0)
                                    .font(.system(size: 20))
                                Text("Lens")
                                Spacer()
                                
                                HStack{
                                    Image(systemName: "square.fill")
                                        .foregroundColor(.blue)
                                        .opacity(0.5)
                                        .font(.system(size: 20))
                                    Text("Binocular")
                                }  
                            }
                        }
                        //.offset(y: 100) // Move the VStack 30% lower
                    } else {
                        Text("Please enter a valid focal length")
                    }
                }
                .navigationTitle("Field of View")
                .navigationBarTitleDisplayMode(.inline)
                .padding()   
            }
            .navigationViewStyle(.stack)  
        }
    }
}

struct FOVTriangle: Shape {
    var fov: Double
    
    func path(in rect: CGRect) -> Path {
        var path = Path()
        
        let radians = fov * (.pi / 180.0)
        let height = Double(rect.height) / 3.1
        let width = tan(radians / 2) * height / 3.1
        let startPoint = CGPoint(x: rect.midX, y: rect.maxY)
        let leftPoint = CGPoint(x: rect.midX - CGFloat(width), y: 0)
        let rightPoint = CGPoint(x: rect.midX + CGFloat(width), y: 0)
        
        path.move(to: startPoint)
        path.addLine(to: leftPoint)
        path.addLine(to: rightPoint)
        path.addLine(to: startPoint)
        
        return path
    }
}
      
#Preview {
    Calc2View()
}

everything :-) No result unfortunately

2

There are 2 answers

0
Amish Tufail On
**Here's your fix:** 

struct Calc2View: View {
    @State private var lensFocalLength: Double
    let sensorWidth = 36.0 // Full frame sensor width

    init() {
        let savedValue = UserDefaults.standard.double(forKey: "selectedLens")
        _lensFocalLength = State(initialValue: savedValue != 0 ? savedValue : 50.0)
    }

    var body: some View {
        NavigationView {
            // ... rest of your view code

            .onAppear {
                // This ensures that the correct value is loaded when the view appears
                let savedValue = UserDefaults.standard.double(forKey: "selectedLens")
                if savedValue != 0 {
                    lensFocalLength = savedValue
                }
            }
        }
    }
}
0
soundflix On

Using @AppStorage will take care of saving to UserDefaults:

struct Calc2View: View {
    @AppStorage("lensFocalLength") var lensFocalLength: Double = 50
    let sensorWidth = 36.0 // Full frame sensor width

    var body: some View {
       // ... your content
    }
}

Note: I usually give the key string the exact same name as the variable.