I've seen lots of documentation about throwing and handling errors in swift overall, but none in the context of swiftui/views.
I have a view that is a collection of uniform Circles(). They need to be drawn randomly without overlap and there could be anywhere from 1 to 100 dots. This app will run on both iPads and iPhones, and even then, each category has different screen sizes and thus can hold a different max amount of dots. The approach I've gone for is effectively placing them in a grid and applying a random "jitter".
As such, I need to be able to throw an error in the code (and consequently inform the user) in the case that the screen is too small for the number of dots they want.
As you'll see in the code, I initially tried utilising a SwiftUI Alert, but "modifying state during view update results in unknown behaviour"
The current compile error is "Invalid conversion from throwing function of type '(GeometryProxy) throws -> some View' to non-throwing function type '(GeometryProxy) -> some View'"
private let diam: CGFloat = 50
struct Dots: View {
@State private var dotAlert = false
let quantity: Int
let dot: some View = Circle()
.size(width: diam, height: diam)
.fill(Color.red)
var offsets: [CGSize] = []
init(_ quantity: Int) {
self.quantity = quantity
}
var body: some View {
VStack {
GeometryReader { geom in
ForEach(try self.genOffsets(maxWidth: geom.size.width, maxHeight: geom.size.height), id: \.self) {offset in
self.dot
.offset(offset)
}
Spacer()
Text("Dots: \(self.quantity)")
}.alert(isPresented: $dotAlert) {
Alert(
title: Text("my alert"),
dismissButton: .cancel()
)
}
}
}
extension Dots {
func genOffsets(maxWidth: CGFloat, maxHeight: CGFloat) throws -> [CGSize] {
var offsets: [CGSize] = []
let separation: CGFloat = 20
let initialPadding: CGFloat = separation
let maxJitter = separation * 0.49
let cols = (maxWidth-initialPadding) / (diam+separation)
let rows = ceil(Double(quantity) / Double(cols))
let maxRows = Double(floor((maxHeight-initialPadding) / (diam+separation)))
print(maxRows)
// if rows > maxRows {
// dotAlert = true
// }
guard rows > maxRows else { throw DotsError.smallScreen }
for r in 0..<Int(rows) {
for c in 0..<Int(cols) {
// Initial padding from TL corner
var x = initialPadding
var y = x
// Separate dot into standard grid slot
x += CGFloat(c)*(diam+separation)
y += CGFloat(r)*(diam+separation)
// Apply jitter
x += CGFloat.random(in: -maxJitter ..< maxJitter)
y += CGFloat.random(in: -maxJitter ..< maxJitter)
offsets.append(CGSize(width: x, height: y))
}
}
return offsets
}
}
extension CGSize: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(width)
hasher.combine(height)
}
}
enum DotsError: Error {
case smallScreen
}