Sergio Bost
Sergio Bost

Reputation: 3209

Crash due index out of range -- although I'm sure index is not out of range

I have a parent view whose child view is any given index of an array. The index of the array is scrolled through by tapping buttons that increment or decrement the index which is stored in a State property.

However when the view is first initialized I get a crash, even though the State's initial value is always 0.

What is going on?

Code can be copied and pasted to reproduce error

import SwiftUI

struct ContentView: View {
    @State private var shouldShowQuotes = false
    var body: some View {
        ZStack {
            Color.orange
            VStack {
                Button(action: showQuotes){
                    Text("Get Quotes").bold()
                        .frame(maxWidth: 300)
                }
                   //  .controlProminence(.increased) //Safe to uncomment if Xcode 13
                    // .buttonStyle(.bordered) 
                    // .controlSize(.large)
            }
            .fullScreenCover(isPresented: $shouldShowQuotes) {
                QuoteScreen()
            }
        }.ignoresSafeArea()
    }
    private func showQuotes() {
        self.shouldShowQuotes.toggle()
    }
}

struct QuoteScreen: View {
    @State private var quoteIndex = 0
    var currentQuote: Quote {
        return dummyData[quoteIndex]
    }
    var body: some View {
        ZStack {
            Color.orange
            VStack {
                QuoteView(quote: currentQuote)
                Spacer()
                HStack {
                    Button(action: degress) {
                        Image(systemName: "arrow.left.square.fill")
                            .resizable()
                        .frame(width: 50, height: 50)
                    }
                    Spacer()
                    Button(action: progress) {
                        Image(systemName: "arrow.right.square.fill")
                            .resizable()
                        .frame(width: 50, height: 50)
                    }
                }
                .padding(28)
                    //.buttonStyle(.plain) Safe to uncomment if Xcode 13
                    
            }
        }.ignoresSafeArea()
    }
    private func progress() {
    quoteIndex += 1
    }
    
    private func degress() {
        quoteIndex -= 1
    }
}

struct QuoteView: View {
    @State private var showQuotes = false
    let quote: Quote
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 25)
                .stroke(lineWidth: 2)
            VStack {
                Text(quote.quote)
                frame(maxWidth: 300)
                Text(quote.author)
                    .frame(maxWidth: 300, alignment: .trailing)
                    .foregroundColor(.secondary)
            }
            
        }.navigationBarHidden(true)
        .frame(height: 400)
            .padding()
    }
}


let dummyData = [Quote(quote: "The apple does not fall far from the tree", author: "Lincoln", index: 1),
                Quote(quote: "Not everything that can be faced can be changed, but be sure that nothing can change until it is faced", author: "Unknown", index: 2),
                 Quote(quote: "Actions are but intentions", author: "Muhammad", index: 3)
    ]

struct Quote: Codable {
    let quote: String
    let author: String
    let index: Int
}

Upvotes: 0

Views: 58

Answers (2)

Ralf Ebert
Ralf Ebert

Reputation: 4082

This crash is not caused by the array access but by a typo in your code. You can see that if you run it in the simulator and look at the stack trace. It gets in an endless loop in the internals of SwiftUI. The reason is the missing dot before the frame modifier:


struct QuoteView: View {
    @State private var showQuotes = false
    let quote: Quote
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 25)
                .stroke(lineWidth: 2)
            VStack {
                Text(quote.quote)
                frame(maxWidth: 300)  << !!!!! missing dot
                Text(quote.author)
                    .frame(maxWidth: 300, alignment: .trailing)
                    .foregroundColor(.secondary)
            }
            
        }.navigationBarHidden(true)
        .frame(height: 400)
            .padding()
    }
}

This calls the frame method on the QuoteView and not on the Text - which is an invalid operation.

Upvotes: 1

When using arrays you always have to check that the element at the chosen index exist. This is how I tested and modify your code to make it work. (note: although this is just a test with dummyData, you need to decide if you want to scroll through the array index, or the Quote-index value, and adjust accordingly)

import SwiftUI


@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

let dummyData = [
    Quote(quote: "the index zero quote", author: "silly-billy", index: 0),
    Quote(quote: "The apple does not fall far from the tree", author: "Lincoln", index: 1),
    Quote(quote: "Not everything that can be faced can be changed, but be sure that nothing can change until it is faced", author: "Unknown", index: 2),
    Quote(quote: "Actions are but intentions", author: "Muhammad", index: 3)
]

struct ContentView: View {
    @State private var shouldShowQuotes = false
    var body: some View {
        ZStack {
            Color.orange
            VStack {
                Button(action: showQuotes){
                    Text("Get Quotes").bold()
                        .frame(maxWidth: 300)
                }
                //  .controlProminence(.increased) //Safe to uncomment if Xcode 13
                // .buttonStyle(.bordered)
                // .controlSize(.large)
            }
            .fullScreenCover(isPresented: $shouldShowQuotes) {
                QuoteScreen()
            }
        }.ignoresSafeArea()
    }
    private func showQuotes() {
        self.shouldShowQuotes.toggle()
    }
}

struct QuoteScreen: View {
    @State private var quoteIndex = 0
    @State var currentQuote: Quote = dummyData[0]  // <--- here, do not use "quoteIndex"

    var body: some View {
        ZStack {
            Color.orange
            VStack {
                QuoteView(quote: $currentQuote) // <--- here
                Spacer()
                HStack {
                    Button(action: degress) {
                        Image(systemName: "arrow.left.square.fill")
                            .resizable()
                            .frame(width: 50, height: 50)
                    }
                    Spacer()
                    Button(action: progress) {
                        Image(systemName: "arrow.right.square.fill")
                            .resizable()
                            .frame(width: 50, height: 50)
                    }
                }
                .padding(28)
                //.buttonStyle(.plain) Safe to uncomment if Xcode 13
                
            }
        }.ignoresSafeArea()
    }
    // you will have to adjust this to your needs
        private func progress() {
    let prevValue = quoteIndex
    quoteIndex += 1
    if let thisQuote = dummyData.first(where: { $0.index == quoteIndex}) { // <--- here
        currentQuote = thisQuote
    } else {
        quoteIndex = prevValue
    }
}
// you will have to adjust this to your needs
private func degress() {
    let prevValue = quoteIndex
    quoteIndex -= 1
    if let thisQuote = dummyData.first(where: { $0.index == quoteIndex}) { // <--- here
        currentQuote = thisQuote
    } else {
        quoteIndex = prevValue
    }
}
}

struct QuoteView: View {
    @State private var showQuotes = false
    @Binding var quote: Quote // <--- here
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 25)
                .stroke(lineWidth: 2)
            VStack {
                Text(quote.quote)
                    .frame(maxWidth: 300) // <--- here missing leading "."
                Text(quote.author)
                    .frame(maxWidth: 300, alignment: .trailing)
                    .foregroundColor(.secondary)
            }
            
        }.navigationBarHidden(true)
            .frame(height: 400)
            .padding()
    }
}

struct Quote: Identifiable {
    let id = UUID()
    var quote: String
    var author: String
    var index: Int
}

Upvotes: 1

Related Questions