2

enter image description here

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>) {
        
    }
    
}
7
  • Saying "it's not working well it gives me problems" isn't clear what the problem is. Is an item not being removed from the bgColors array? Is the view not updating? Commented Oct 24, 2020 at 14:02
  • Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444 Commented Oct 24, 2020 at 14:04
  • First advice is that not every variable in your view needs to be a State, like the url, its never changed. Also, It would be good if you could reduce your program code so that the focus is on the problem. Commented Oct 24, 2020 at 14:04
  • @Paul What is line 444? Is it ColorPicker("Set the background color", selection: $bgColors[index])? Commented Oct 24, 2020 at 14:05
  • @mamaessen: Many times they ask me for the complete code to test it, however the interested part is only where it is if self.select == 2 { Commented Oct 24, 2020 at 14:06

1 Answer 1

3

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)
    }
}
Sign up to request clarification or add additional context in comments.

8 Comments

I tried as you say, when I remove last element of the array it gives me the following error: Fatal error: Index out of range
@Paul Didn't notice that when testing, but it is fixed now.
What changes compared to before?
You can see by clicking the edit history, but the change is I replaced $arr[index] with Binding<Color>(...). It no longer crashes now when you remove the last item
I was wondering what the difference between before and now, that is, what was the problem?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.