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.

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.

Accepted Answer

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.

Problem when using "if" statement in SwiftUI
 
 
Q