How SwiftUI onChange modifier works?
Asked Answered
S

3

5

I populated a SwiftUI Picker dynamically. The Picker is working successfully. I want to trigger or listen picker changing event. Suppose I will print the value if an user select an item. My iOS deployment target is 14, I found that iOS has an inbuilt function onChange to detect this kind of listener. I used that modifier but not working. Here is my code:

var body: some View {
    
    
    
    Picker( selection: $selectedStrength, label: Text("Status")) {
        
        ForEach(courseList, id: \.self) { item in
            
            Text(item.courseCode ?? "")
        }
    }
    .onChange(of:selectedStrength, perform: { _ in
        
        print("Value Changed!")
        
    })
    .pickerStyle(WheelPickerStyle())
    
}
Stilwell answered 15/3, 2021 at 7:27 Comment(2)
Do you mean that print("Value Changed!") is not called on selecting any item?Pumice
Yes, print("Value Changed!") not workingStilwell
S
2

Summery: I called a REST API and parse the data, then I tried to create a SwiftUI Picker from those data.

Solution: After searching a lot, I found the right solution. I was populating the Picker directly from Json object data. I converted those data into SwiftUI string array and tried onChange function, its working now.

My Code :

import SwiftUI

struct TeacherCourseListParser: Decodable, Hashable{
    
    var totalStudent: Int?
    var courseName: String?
    var courseCode: String?
    var courseId: Int?
    var courseTeacherEnrollId: Int?
    
    
}


    

struct Test: View {
    
    @State var courseList = [TeacherCourseListParser]()
          
    @State private var selectedStrength = ""
    @State var courseCodeTempArray = [String]()
    
    var body: some View {
        
        VStack(spacing: 0){
            
            
            Picker( selection: $selectedStrength, label: Text("Course Code")) {
                
                ForEach(courseCodeTempArray, id: \.self) {
                    
                   
                    Text($0)
                    
                    
                }
            }
            .onChange(of: selectedStrength, perform: { _ in
                
                print("Value Changed!")
            })
            .pickerStyle(WheelPickerStyle())
            
            
            
            
            
        }
        
    }

Here is the function how I fetched the JSON object :

func FetchTeacherCourseCode(){
        
        
        
        
        let token = UserDefaults.standard.string(forKey: "login_token")
      
        
        // create post request
                                                      
        guard let url = URL(string: Constant.mainServerUrl+Constant.getTeacherCourseList) else {
            print("Invalid URL")
            return
        }
        
        print(url)
        
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        // request.httpBody = jsonData
        request.allHTTPHeaderFields = [
            "Content-Type": "application/json",
            "Accept": "application/json",
            "Authorization": "Bearer "+token!
        ]
        
        URLSession.shared.dataTask(with: request) {data, response, error in
            if let data = data {
                do {
                                                                                        
                    //-- Parse response according to the object
                    let detailedObjectFetcher = try! JSONDecoder().decode([TeacherCourseListParser].self, from: data)
                   
                    
                                                                    
                    DispatchQueue.main.async {
                        
                        for i in detailedObjectFetcher {
                            
                            // instead of populating Picker from JSON object directly I generated a string array                                                     
                            courseCodeTempArray.append(i.courseCode ?? "")
                            
                        }
                        
                       self.courseList = detailedObjectFetcher
                        showProgressBar = false
                        
                        
                    }
                    
                    
                    
                    
                } catch DecodingError.keyNotFound(let key, let context) {
                    Swift.print("could not find key \(key) in JSON: \(context.debugDescription)")
                } catch DecodingError.valueNotFound(let type, let context) {
                    Swift.print("could not find type \(type) in JSON: \(context.debugDescription)")
                } catch DecodingError.typeMismatch(let type, let context) {
                    Swift.print("type mismatch for type \(type) in JSON: \(context.debugDescription)")
                } catch DecodingError.dataCorrupted(let context) {
                    Swift.print("data found to be corrupted in JSON: \(context.debugDescription)")
                } catch let error as NSError {
                    NSLog("Error in read(from:ofType:) domain= \(error.domain), description= \(error.localizedDescription)")
                }
            }
            // print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
            
        }.resume()
        
        
        
    }
}
Stilwell answered 16/3, 2021 at 4:7 Comment(0)
K
4

I think it is due to type mismatch, Picker selection type and item type or (!) tag should be the same. As soon as you iterate by items but shows courseCode that is probably the reason of bug.

Try something like (not tested as provided code is not standalone and not runnable)

Picker(selection: $selectedStrength, label: Text("Status")) {
    
    ForEach(courseList, id: \.self) { item in
        
       // assiming selectedStrength type and item.courseCode type is the same
        Text(item.courseCode ?? "").tag(item.courseCode)
    }
}
.onChange(of: selectedStrength, perform: { _ in
    
    print("Value Changed!")
    
})
Kudva answered 15/3, 2021 at 15:27 Comment(0)
S
2

Summery: I called a REST API and parse the data, then I tried to create a SwiftUI Picker from those data.

Solution: After searching a lot, I found the right solution. I was populating the Picker directly from Json object data. I converted those data into SwiftUI string array and tried onChange function, its working now.

My Code :

import SwiftUI

struct TeacherCourseListParser: Decodable, Hashable{
    
    var totalStudent: Int?
    var courseName: String?
    var courseCode: String?
    var courseId: Int?
    var courseTeacherEnrollId: Int?
    
    
}


    

struct Test: View {
    
    @State var courseList = [TeacherCourseListParser]()
          
    @State private var selectedStrength = ""
    @State var courseCodeTempArray = [String]()
    
    var body: some View {
        
        VStack(spacing: 0){
            
            
            Picker( selection: $selectedStrength, label: Text("Course Code")) {
                
                ForEach(courseCodeTempArray, id: \.self) {
                    
                   
                    Text($0)
                    
                    
                }
            }
            .onChange(of: selectedStrength, perform: { _ in
                
                print("Value Changed!")
            })
            .pickerStyle(WheelPickerStyle())
            
            
            
            
            
        }
        
    }

Here is the function how I fetched the JSON object :

func FetchTeacherCourseCode(){
        
        
        
        
        let token = UserDefaults.standard.string(forKey: "login_token")
      
        
        // create post request
                                                      
        guard let url = URL(string: Constant.mainServerUrl+Constant.getTeacherCourseList) else {
            print("Invalid URL")
            return
        }
        
        print(url)
        
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        // request.httpBody = jsonData
        request.allHTTPHeaderFields = [
            "Content-Type": "application/json",
            "Accept": "application/json",
            "Authorization": "Bearer "+token!
        ]
        
        URLSession.shared.dataTask(with: request) {data, response, error in
            if let data = data {
                do {
                                                                                        
                    //-- Parse response according to the object
                    let detailedObjectFetcher = try! JSONDecoder().decode([TeacherCourseListParser].self, from: data)
                   
                    
                                                                    
                    DispatchQueue.main.async {
                        
                        for i in detailedObjectFetcher {
                            
                            // instead of populating Picker from JSON object directly I generated a string array                                                     
                            courseCodeTempArray.append(i.courseCode ?? "")
                            
                        }
                        
                       self.courseList = detailedObjectFetcher
                        showProgressBar = false
                        
                        
                    }
                    
                    
                    
                    
                } catch DecodingError.keyNotFound(let key, let context) {
                    Swift.print("could not find key \(key) in JSON: \(context.debugDescription)")
                } catch DecodingError.valueNotFound(let type, let context) {
                    Swift.print("could not find type \(type) in JSON: \(context.debugDescription)")
                } catch DecodingError.typeMismatch(let type, let context) {
                    Swift.print("type mismatch for type \(type) in JSON: \(context.debugDescription)")
                } catch DecodingError.dataCorrupted(let context) {
                    Swift.print("data found to be corrupted in JSON: \(context.debugDescription)")
                } catch let error as NSError {
                    NSLog("Error in read(from:ofType:) domain= \(error.domain), description= \(error.localizedDescription)")
                }
            }
            // print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
            
        }.resume()
        
        
        
    }
}
Stilwell answered 16/3, 2021 at 4:7 Comment(0)
P
1

Try below code for implementation:

    struct ContentView: View {
        var colors = ["Red", "Green", "Blue", "Tartan"]
        @State private var selectedColor = "Red"
    
        var body: some View {
            VStack {
                Picker("Please choose a color", selection: $selectedColor) {
                    ForEach(colors, id: \.self) {
                        Text($0)
                    }
                }
                Text("You selected: \(selectedColor)")
            }
      

  }
}
Pumice answered 15/3, 2021 at 12:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.