Problem when using "if" statement in SwiftUI

I'm using SwiftUI to create my app, and I want to use "if" statement to conditionally add a View to a VStack, but the following code fails to compile in my project:


struct BookItemGroupVertical: View { 
// BudgetBook is a custom class  
    var items: [BudgetBook]
    
    private var halfNumber: Int {
        return Int(items.count / 2)
    }
    
    var body: some View {
        VStack(alignment: .leading) {
            ForEach(0 ..< self.halfNumber) { index in
// BookItemGroupHorizontal is a custom View
                BookItemGroupHorizontal(items: [self.items[index * 2], self.items[index * 2 + 1]])
            }
// This is where the problem happens
            if items.count % 2 == 1 {
                Text("it")
            }
        }
    }
}


However, I've tried a simpler sample and it works:


struct ConditionalTestView: View {
    var array = [1, 2, 3, 4, 5]
    var body: some View {
        VStack {
            ForEach(0 ..< 5) { _ in
                Text("Something")
            }
            if array.count % 2 == 1 {
                Text("Second Line")
            }
        }
    }
}


I'm wondering whether this is a bug of SwiftUI. If not, how can I solve this problem?


Thanks.

Accepted Reply

Thanks for showing your code, I could have reproduced the same error (it was shown at line 80. but that may not be a big issue).

I also have found that your code compiles with the fix shown in my last post, with Xcode 11.5.

Replies

What is the error message you get ?


Could you show the code for custom class ?


I tried the following, just to check it works.

It is a custom (yet extremely simple) custom view with a Binding var :


struct MyView: View {
    @Binding var value: Int
    var body: some View {
         Text("Something new \(value)")
    }
}

struct ContentView: View {
    var array = [1, 2, 3, 4, 5]
    private var halfNumber: Int {
        return Int(array.count / 2)
    }
    @State var newValue : [Int] = [10, 20, 30, 40, 50]
    var body: some View {
        VStack {
            ForEach(0 ..< self.halfNumber) { i in
                MyView(value: self.$newValue[i]) 
            }
            if array.count % 2 == 1 {
                Text("Second Line")
            }
        }
    }
}

the following code fails to compile

You should better show the error message, and more details to reproduce the issue.


As you found, a slight change make your code compile successfully, so some this-code-compiles examples would not solve your problem.


As far as I tried, this code compiles.

    var body: some View {
        VStack(alignment: .leading) {
            ForEach(0 ..< self.halfNumber) { index in
                BookItemGroupHorizontal(items: [self.items[index * 2], self.items[index * 2 + 1]] as [BudgetBook])
            }
            if items.count % 2 == 1 {
                Text("it")
            }
        }
    }

But I do not know what's happening inside the Swift compiler and cannot be sure if this solves your issue or not.

Thanks Claude31 and OOPer. Here's my full code: (quite long and may be a little bit confusing)


(1). My custom class:


class BudgetItem {
    var name: String
    var date: Date
    var category: String
    var amount: Double
    var note: String
    
    init(name: String = "New Item", date: Date = Date(), category: String = "Unknown", amount: Double = 0.0, note: String = "A new budget item") {
        self.name = name
        self.date = date
        self.category = category
        self.amount = amount
        self.note = note
    }
}

class BudgetBook {
    var title: String
    var color: String
    var items: [BudgetItem]
    
    init(title: String = "New Book", color: String = "blue", items: [BudgetItem] = []) {
        self.title = title
        self.color = color
        self.items = items
    }
}


(2). BookItemView.swift

struct BookItemView: View {
    var title: String
    var color: Color
    
    var body: some View {
        ZStack {
            BookPageView(color: color)
            BookArcView()
            VStack {
                Text(title).font(.title).lineLimit(2)
                Image(systemName: "\(title.first?.lowercased() ?? "a").circle").scaleEffect(2)
            }
        }
    }
}

struct BookPageView: View {
    var color: Color
    
    var body: some View {
        GeometryReader { geometry in
            Path { path in
                path.move(to: .zero)
                path.addLine(to: CGPoint(x: geometry.size.width - 40, y: 0))
                path.addQuadCurve(to: CGPoint(x: geometry.size.width, y: 40), control: CGPoint(x: geometry.size.width, y: 0))
                path.addLine(to: CGPoint(x: geometry.size.width, y: geometry.size.height - 40))
                path.addQuadCurve(to: CGPoint(x: geometry.size.width - 40, y: geometry.size.height), control: CGPoint(x: geometry.size.width, y: geometry.size.height))
                path.addLine(to: CGPoint(x: 0, y: geometry.size.height))
                path.addLine(to: .zero)
            }.fill(self.color)
        }
    }
}

struct BookArcView: View {
    var body: some View {
        GeometryReader { geometry in
            VStack {
                ForEach(0 ..< 5) { _ in
                    ArcView(
                        startPoint: CGPoint(x: 0, y: 5),
                        diameter: geometry.size.height / 10,
                        length: 10
                    )
                }
            }
        }
    }
}

struct ArcView: View {
    var startPoint: CGPoint
    var diameter: CGFloat
    var length: CGFloat
    
    var body: some View {
        Path { path in
            path.move(to: self.startPoint)
            path.addCurve(
                to: CGPoint(x: self.startPoint.x, y: self.startPoint.y + self.diameter),
                control1: CGPoint(x: self.startPoint.x - self.diameter * 0.6, y: self.startPoint.y),
                control2: CGPoint(x: self.startPoint.x - self.diameter * 0.6, y: self.startPoint.y + self.diameter))
            path.addLine(to: CGPoint(x: self.startPoint.x + self.length, y: self.startPoint.y + self.diameter))
        }.stroke(lineWidth: 3)
    }
}


(3). BudgetView.swift

struct BudgetView: View {
    
    @State var showPersonalCenter = false
    
    @State var books: [BudgetBook] = [
        BudgetBook(title: "Daily", color: "blue", items: []),
        BudgetBook(title: "Electronic", color: "red", items: []),
        BudgetBook(title: "Grocery", color: "green", items: [])
    ]
    
    private var numberOfBooks: Int {
        return books.count
    }
    
    var body: some View {
        NavigationView {
            ScrollView {
                VStack {
                    Button(action: { self.createNewBook() }) {
                        Text("+ Create a New Book")
                            .foregroundColor(.white)
                            .padding()
                    }.background(Color.gray).cornerRadius(10)
                    
                    VStack(alignment: .leading) {
                        BookItemGroupVertical(items: self.books)
                    }
                    
                }
            }
            .navigationBarTitle("Budget")
            .navigationBarItems(trailing:
                Image(systemName: "person.crop.circle")
                    .scaleEffect(1.5)
                    .foregroundColor(.blue)
                    .onTapGesture {
                        self.showPersonalCenter.toggle()
                }
                    .sheet(isPresented: $showPersonalCenter) { PersonalCenterView() }
            )
        }
    }
    
    func createNewBook() {
        //TODO: Create a new budget book
    }
}

struct BookItemGroupHorizontal: View {
    
    var items: [BudgetBook]
    
    private var number: Int {
        return items.count
    }
    
    var body: some View {
        HStack {
            ForEach(0 ..< number) { index in
                NavigationLink(destination: BudgetDetailView()) {
                    BookItemView(title: self.items[index].title, color: .color(from: self.items[index].color))
                        .frame(width: 150, height: 200)
                        .padding()
                }.accentColor(.primary)
            }
            
        }
    }
}

struct BookItemGroupVertical: View {
    
    var items: [BudgetBook]
    
    private var halfNumber: Int {
        return Int(items.count / 2)
    }
    
    var body: some View {
        VStack(alignment: .leading) {
            ForEach(0 ..< self.halfNumber) { index in
                BookItemGroupHorizontal(items: [self.items[index * 2], self.items[index * 2 + 1]])
            }
            if items.count % 2 == 1 {
                Text("it")
            }
        }
    }
}


And here's is the complier error (line 84 in BudgetView.swift):



I did decompose my custom view into smaller subviews but the compile still cannot type-check the view.

Thanks Claude31. I post my original code below.

Thanks OOPer. I post my original code and compiler error below.

Thanks for showing your code, I could have reproduced the same error (it was shown at line 80. but that may not be a big issue).

I also have found that your code compiles with the fix shown in my last post, with Xcode 11.5.

Amazing! Adding a "as" really solves the problem. Thanks a lot!


I guess that the reason may be that SwiftUI cannot type-check the "items" parameter.


Again, thank you for solving my problem.

Happy to hear that solved your issue.


I guess that the reason may be that SwiftUI cannot type-check the "items" parameter.


The error message shown was


The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions


When Swift shows this sort of messages where you cannot find what is wrong, adding some explicit type annotation is one thing worth trying.