Background
In this learning app I would like to deliver functionality that moves a point to a random location until interrupted.
App
The interface is very simple and contains of two buttons that Start/Stop the animation or Reset/Center position.
Code
The full application code is provided below.
//
// ContentView.swift
// BouncingBall
//
// Sample script providing endless animation of bouncing ball.
//
// Created by Konrad on 24/07/2021.
//
import SwiftUI
// Simple UI button
struct SimpleButton: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.padding(.horizontal)
.background(Color(.lightGray))
.clipShape(Capsule())
}
}
func randomiseXY(xMax: CGFloat, yMax: CGFloat) -> (CGFloat, CGFloat) {
let randX = CGFloat.random(in: 0...xMax)
let randY = CGFloat.random(in: 0...yMax)
return (randX, randY)
}
func makeCircleView(_ geometry: GeometryProxy, randomPosition: Bool) -> some View {
// Original idea: https://stackoverflow.com/a/57577752/1655567
// Define initial position in the centre
var posX: CGFloat
var posY: CGFloat
if randomPosition {
(posX, posY) = randomiseXY(xMax: geometry.size.width, yMax: geometry.size.height)
} else {
posX = (geometry.size.width - geometry.size.width / 7) / 2
posY = (geometry.size.height - geometry.size.height / 7) / 2
}
let circleWithLabels =
VStack {
HStack {
Text("X: \(posX)")
Text("Y: \(posY)")
}
Circle()
.path(in: CGRect(x: posX, y: posY,
width: CGFloat(geometry.size.width / 7),
height: CGFloat(geometry.size.height / 7))
)
}
return circleWithLabels
}
struct ContentView: View {
@State private var currentlyRunning: Bool = false
var body: some View {
VStack {
HStack {
if currentlyRunning {
Button("Stop") {
self.currentlyRunning = false
}
.buttonStyle(SimpleButton())
} else {
Button("Start") {
self.currentlyRunning.toggle()
}
.buttonStyle(SimpleButton())
}
Button("Reset") {
self.currentlyRunning = false
}
.buttonStyle(SimpleButton())
}
GeometryReader { geometry in
while currentlyRunning {
makeCircleView(geometry, randomPosition: currentlyRunning)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Problem
The offending bit relates to my attempt of trying to use the ViewBuilder within the flow control:
GeometryReader { geometry in
while currentlyRunning {
makeCircleView(geometry, randomPosition: currentlyRunning)
}
}
This results in the following error:
Closure containing control flow statement cannot be used with result builder 'ViewBuilder'
Question
How can I work around it so I can force the partial re-creation of the View, until the condition changes?
Side questions
- As I'm starting with swift/swiftui any broader pointers on good coding practice in Swift are most welcome.
With the offending bit removed as follows:
GeometryReader { geometry in
//while currentlyRunning {
makeCircleView(geometry, randomPosition: currentlyRunning)
//}
}
I can keep on manually refreshing the view; however the dot gets cantered every so often due to the randomPosition
taking the false
value.