Mathematical Lie
Mathematical Lie

Reputation: 143

Where have misused a type?

I'm having a beginner types problem in swift and the error message isn't very helpful. I get the error

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

from the following code:

import Foundation
import SwiftUI
import CoreGraphics

struct SwiftUIView: View {
    let alphabet = ["a","b","c","d","e"]
    
    
    var body: some View {
        GeometryReader { geometry in
            let clockPadding = CGFloat(20)
            let width = min(geometry.size.width, geometry.size.height)
            let diam = width - 2*clockPadding
            let centre = width/2
            let radius = diam/2
            let theta: CGFloat = 2*CGFloat.pi/CGFloat(alphabet.count)
            
            ForEach(0..<alphabet.count) { i in
                let x = centre + radius * cos(i*theta)
                let y = centre + radius * sin(i*theta)
                Text(alphabet[i])
                    .position(x: x, y: y)
            }          
        }
    }
}

In fact the error is still there even when I make the trig functions take a constant, changing the ForEach to:

ForEach(0..<alphabet.count) { i in
     let x = centre + radius * cos(0*theta)
     let y = centre + radius * sin(0*theta)
     Text(alphabet[0])
     .position(x: x, y: y)
}   

What I'm trying to do is arrange the letter evenly around a circle.

Upvotes: 4

Views: 92

Answers (1)

Rob Napier
Rob Napier

Reputation: 299663

The compiler is having trouble with the + and * in your x and y assignments. You need to let it know they're CGFloats, and then you need to turn i into a CGFloat as well (which is a bug in your code, but the + and * overloads are just too hard on the compiler and it couldn't help you find it).

        ForEach(0..<alphabet.count) { i in
            let x: CGFloat = centre + radius * cos(CGFloat(i)*theta)
            let y: CGFloat = centre + radius * sin(CGFloat(i)*theta)
            Text(alphabet[i])
                .position(x: x, y: y)
        }

As with most SwiftUI compile problems, the way to debug this is to take things out until it works, and then add things in very slowly. I took out the let x, let y and position lines, and it compiles, so I know the top part is fine. Then I added back in just the let x line and I got the error (Cannot convert value of type 'Int' to expected argument type 'CGFloat'). I fixed the bug for x and y and it compiled (without position). When I added the .position, it was too complicated again, so I added CGFloat types to x and y.

Another approach that often helps is to split things up into smaller functions. For example:

struct SwiftUIView: View {
    let alphabet = ["a","b","c","d","e"]

    private typealias Layout = (centre: CGFloat, radius: CGFloat, theta: CGFloat)

    private func makeLayout(for geometry: GeometryProxy) -> Layout {
        let clockPadding = CGFloat(20)
        let width = min(geometry.size.width, geometry.size.height)
        let diam = width - 2*clockPadding
        let centre = width/2
        let radius = diam/2
        let theta = 2*CGFloat.pi/CGFloat(alphabet.count)
        return (centre: centre, radius: radius, theta: theta)
    }

    private func positionedText(_ string: String, for l: Layout, at index: Int) -> some View {
        let i = CGFloat(index)
        return Text(string)
            .position(x: l.centre + l.radius * cos(i*l.theta),
                      y: l.centre + l.radius * sin(i*l.theta))
    }

    var body: some View {
        GeometryReader { geometry in
            let layout = makeLayout(for: geometry)
            ForEach(0..<alphabet.count) { i in
                positionedText(alphabet[i], for: layout, at: i)
            }
        }
    }
}

If you wrote it that way, the compiler would have given you a much more useful error message for the missing CGFloat conversion.

Upvotes: 3

Related Questions