Function in Swift - swift

I am trying to learn swift by building out a very basic barbell weight plate calculator - the user enters a number in the text field and it displays the plates needed.
I wrote a function in Swift Playgrounds that works well for I am trying to do, but I don't understand how to move it into an app view where the user enters a number and it filters.
I have tried looking online for an explanation to this without any luck: Here is my Swift Playground code which is ideally what I would like to use:
import UIKit
func barbellweight (weight: Int){
var plate_hash : [Int:Int] = [:]
if weight == 45 {
print("You only need the bar!")
}else if weight < 45{
print("Must be divisible by 5!")
}else if (weight % 5 != 0){
print("Must be divisible by 5!")
}else{
let plate_array = [45, 35, 25, 10, 5, 2.5]
var one_side_weight = Double(weight - 45) / 2.0
for plate_size in plate_array {
var plate_amount = (one_side_weight / plate_size)
plate_amount.round(.towardZero)
one_side_weight -= (plate_size * plate_amount)
plate_hash[Int(plate_size)] = Int(plate_amount)
}
}
let plate_hash_filtered = plate_hash.filter { $0.value > 0 }
//print(plate_hash_filtered)
print(plate_hash_filtered)
}
barbellweight(weight: 225)
Here is attempt to implement it in Swift UI but without any luck. I know it's deconstructed and slightly different - I don't quite understand how to integrate a function into SwiftUI. If someone has any recommendations for resources to look at for this specific ask I would really appreciate it.
import SwiftUI
struct Weight_Plate: View {
#State var weight: String = "135"
#State var plate_hash = [String]()
#State var plate_array = [45, 35, 25, 10, 5, 2.5]
var body: some View {
var one_side_weight = Double(Int(weight)! - 45) / 2.0
List{
Text("Number of Plates Needed Per Side")
.multilineTextAlignment(.center)
ForEach(self.plate_array, id: \.self) { plate_size in
var plate_amount = (one_side_weight / plate_size)
if Int(weight) == 45 {
Text("You only need the bar!")
} else if Int(weight)! < 45 {
Text("Must be divisible by 5!")
} else if (Int(weight)! % 5 != 0) {
Text("Must be divisible by 5!")
} else {
//Text("Error")
plate_amount.round(.towardZero)
one_side_weight -= (plate_size * plate_amount)
Text("\(Int(plate_size)) x \(Int(plate_amount))")
// Text("\(plate):\(Int(plate_amount))")
}
}
HStack(alignment: .center) {
Text("Weight:")
.font(.callout)
.bold()
TextField("Enter Desired Weight", text: $weight)
.textFieldStyle(RoundedBorderTextFieldStyle())
}.padding()
}
}
}
struct Weight_Plate_Previews: PreviewProvider {
static var previews: some View {
Weight_Plate()
}
}
I appreciate any help and recommendations on references that would assist me with this. Thank you!

You know you can still define and call functions right?
Change your code to this
import SwiftUI
func barbellWeight (weight: Int) -> [String] { // naming convention in Swift is camelcase
var plate_hash = [Int: Int]()
if weight == 45 {
return ["You only need the bar!"]
} else if weight < 45 {
return ["Insufficient weight!"]
} else if weight % 5 != 0 {
return ["Must be divisible by 5!"]
} else {
let plate_array = [45, 35, 25, 10, 5, 2.5]
var one_side_weight = Double(weight - 45) / 2.0
for plate_size in plate_array {
var plate_amount = (one_side_weight / plate_size)
plate_amount.round(.towardZero)
one_side_weight -= plate_size * plate_amount
plate_hash[Int(plate_size)] = Int(plate_amount)
}
}
return plate_hash.compactMap {
if $0.value < 0 {
return nil
} else {
return "\($0.key): \($0.value)"
}
}
}
struct Weight_Plate: View {
#State var weight = "135"
var body: some View {
List {
ForEach(barbellWeight(weight: Int(weight) ?? 135), id: \.self) {
Text($0)
}
}
HStack(alignment: .center) {
Text("Weight:")
.font(.callout)
.bold()
TextField("Enter Desired Weight", text: $weight)
.textFieldStyle(RoundedBorderTextFieldStyle())
}.padding()
}
}
struct Weight_Plate_Previews: PreviewProvider {
static var previews: some View {
Weight_Plate()
}
}
Of course you can play around with this and make it look better. There are also other ways of doing this that could be more efficient, but this works.
Edit: You just define functions the same way you normally would! I rewrote your function to return an array of strings, each string corresponding to a plate, count pair as you can see when you run it. Since weight is a #State variable, when you change it by accepting your user's input, SwiftUI automatically reloads any view dependent on that variable. This causes the ForEach to be reloaded and your function to be called again. ForEach accepts an array of items, so that's why I glued the key and value of the dictionary together. Maybe this clears it up a little bit.
For help learning SwiftUI, check out Paul Hudson's site. It's how I started learning SwiftUI and how I transitioned to UIKit.
https://www.hackingwithswift.com/100/swiftui

Related

Using DidSet on array of Strings to have character limit doesn't work

I am trying to create a character limit for a TextField which corresponds to a String within an array with the following code but it doesn't seem to work. The following object is a poll object where the user can add TextFields in which they can write in the option they desire. I'm trying to build a max character limit for each string in which if a TextField exceed the value of the max character limit, the array of Strings (or the violating string) is replaced with the old value. Right now, the print statements indicate that the values are being updated and fixed at the max character limit, but on the View, I can still type beyond the max character limit which is odd since the value in the TextField should be bound to the published variable.
VStack {
ForEach(0..<newPollVM.pollOptions.count, id: \.self) { i in
HStack {
TextField("Option \(i + 1)", text: $newPollVM.pollOptions[i])
.font(.title3)
.padding(5)
.overlay(RoundedRectangle(cornerRadius: 5).stroke(Color(UIColor.systemGray), lineWidth: 2))
Spacer()
if newPollVM.pollOptions.count > 2 {
Button {
print("DEBUG: Remove row")
newPollVM.pollOptions.remove(at: i)
} label: {
Image(systemName: "delete.left")
.foregroundColor(.red)
}
}
}
.padding(.top, 5)
}
if newPollVM.pollOptions.count < 6 {
Button {
print("DEBUG: Add Option")
newPollVM.pollOptions.append("")
print("DEBUG: \(newPollVM.pollOptions)")
} label: {
Text("Add")
.foregroundColor(.green)
}
}
class NewPollViewModel: ObservableObject {
let characterLimit = 5
#Published var allowSkipVoting : Bool = false
#Published var pollOptions : [String] = [""] {
didSet {
for (index, pollOption) in pollOptions.enumerated() {
if pollOption.count > characterLimit && oldValue[index].count <= characterLimit {
print("DEBUG: \(oldValue)")
self.pollOptions[index] = oldValue[index]
print("DEBUG: \(pollOptions)")
}
}
}
}
func reset() {
self.allowSkipVoting = false
self.pollOptions = ["", ""]
}
}
However, the following code works regarding setting a character limit for a single String.
TextArea("What's on your mind?", text: $newPostVM.title)
.font(.title)
class NewPostViewModel: ObservableObject {
let characterLimit = 180
#Published var title : String = "" {
didSet {
if title.count > characterLimit && oldValue.count <= characterLimit {
title = oldValue
}
}
}
}
This isn't what you asked for exactly. But I think it solves your problem in a better way. You can just use the limit field on your UITextField. Something like...
yourTextField.textLimit(existingText: textField.text,
newText: string,
limit: 180)
Here is a helpful link that shows a good way to do it.

SwiftUI: How can I update an array of [struct]'s variable with a #Binding?

To preface, I'm nearly one month into learning SwiftUI and I've been watching a few YouTube channels to learn (e.g. Swiftful Thinking (Paul Hudson), Hacking with Swift (Nick Sarno), Sean Allen, etc.).
I'm testing something in Playgrounds and I need to implement a #Binding variable and, as such, I've watched all three of the aforementioned channel's tutorials on #Binding. The tutorials are explained clearly and I understand the concept, but I still seem to be running into a wall
I have a struct which contains a View — named TactileTrigger — that creates a draggable Circle(). I want to send the coordinates of this drag to another struct which contains a View — named CreateNewTrigger — that actually creates new instances of the TactileTrigger struct based on an array of information.
From within the CreateNewTrigger struct, I receive the error message: Cannot use instance member 'bindingXY' within property initializer; property initializers run before 'self' is available.
I've searched StackOverflow and have seen this same error, and I did try to implement an init() within the struct but, apparently, I'm still doing something wrong. Therefore, I have removed the init().
To clarify, I need the [TrigInformation]'s XY value updated for each respective $binding. I made a mockup in SwiftUI as an example of what I'm after:
CreateNewTactileTrigger:
import SwiftUI
class NotesManager: ObservableObject {
#Published var manager: [TrigInformation] = [
TrigInformation(trigNumber: 1,
trigType: .note,
noteValue: .Db,
XY: //<-- NEEDS TO APPEND HERE
),
TrigInformation(trigNumber: 2,
trigType: .note,
noteValue: .C,
XY: //<-- NEEDS TO APPEND HERE
),
TrigInformation(trigNumber: 3,
trigType: .note,
noteValue: .Eb,
XY: //<-- NEEDS TO APPEND HERE
),
TrigInformation(trigNumber: 4,
trigType: .trigger,
XY: //<-- NEEDS TO APPEND HERE
)
]
}
struct CreateNewTactileTrigger: View {
#StateObject var notesManager = NotesManager()
var body: some View {
VStack {
ForEach($notesManager.manager) { $note in
TactileTrigger(label: "\(note.trigNumber.description): [\(note.noteValue?.rawValue ?? "T")]",
bindingXY: $note.XY)
.frame(width: 25, height: 25)
.onAppear {
// notesManager.manager.append(
// TrigInformation(trigNumber: note.trigNumber,
// trigType: note.trigType,. <-- SOMETHING LIKE THIS
// noteValue: note.noteValue,
// XY: note.XY)
// )
}
VStack {
Text("\(note.trigNumber)")
Text("\(note.trigType.rawValue)")
Text("\(note.noteValue?.rawValue ?? "—")")
Text("X: \(note.XY.x)")
Text("Y: \(note.XY.y)")
}
.font(.caption)
.foregroundColor(.white)
.offset(x: 25,
y: 25)
}
}
}
}
struct TrigInformation: Identifiable {
let id = UUID()
var trigNumber: Int
var trigType: TrigType
var noteValue: Notes?
var XY: CGPoint
}
enum TrigType: String {
case trigger
case note
}
enum Notes: String {
case Ab = "Ab"
case A = "A"
case Bb = "Bb"
case B = "B"
case C = "C"
case Db = "Db"
case D = "D"
case Eb = "Eb"
case E = "E"
case F = "F"
case Gb = "Gb"
case G = "G"
}
Tactile Trigger:
import SwiftUI
struct TactileTrigger: View {
#State var label: String = ""
#State var setLocation: CGPoint = CGPoint(x: 100,
y: 100)
#Binding var bindingXY: CGPoint
var body: some View {
ZStack {
Circle()
.fill(.blue)
.overlay(
Text("\(label)").bold()
.font(.subheadline)
.foregroundColor(.white)
)
.frame(width: 75,
height: 75)
.position(x: setLocation.x,
y: setLocation.y)
.gesture(
DragGesture()
.onChanged({ currentPosition in
calculateDrag(value: currentPosition)
})
.onEnded({ endPosition in
calculateDrag(value: endPosition)
})
)
}
}
func calculateDrag(value: DragGesture.Value) {
let coordinates = CGPoint(x: value.location.x,
y: value.location.y)
setLocation = CGPoint(x: coordinates.x,
y: coordinates.y)
// BINDING VARIABLE
bindingXY = setLocation
}
}
MyApp:
import SwiftUI
#main
struct MyApp: App {
var body: some Scene {
WindowGroup {
CreateNewTactileTrigger()
}
}
}
At first every state must be initialized (you have uninitialised so error), at second you try to bind all notes to one state (even if you'd fix first error, you would got wrong behavior). You need own binding for each note in ForEach.
But better to bind directly to model.
So,
struct CreateNewTactileTrigger: View {
// #State var bindingXY: CGPoint // << remove !!
NoteInformation(trigNumber: 1,
trigType: .note,
noteValue: .Db,
XY: .zero // << initialize !!
),
ForEach($notes) { $note in // << work with bindings
TactileTrigger(label: "\(note.trigNumber.description): [\(note.noteValue?.rawValue ?? "T")]",
bindingXY: $note.XY) // << bind directly
no more changes, tested with Xcode 13.4 / iOS 15.5
I've rekindled my deleted answer and updated it to cater for your new question about an array of [TrigInformation].
Try this approach (works well for me), where you have a class NotesManager: ObservableObject, that holds your array of [TrigInformation]. As the trigArray element changes, the ObservableObject will be notified
and the UI refreshed with the new XY positions.
// -- here
class NotesManager: ObservableObject {
#Published var trigArray: [TrigInformation] = [
TrigInformation(trigNumber: 1,trigType: .note,noteValue: .Db, XY: CGPoint(x: 123, y: 123)),
TrigInformation(trigNumber: 2,trigType: .note,noteValue: .C,XY: CGPoint(x: 456, y: 456)),
TrigInformation(trigNumber: 3,trigType: .note,noteValue: .Eb,XY: CGPoint(x: 789, y: 789)),
TrigInformation(trigNumber: 4,trigType: .trigger, XY: CGPoint(x: 101, y: 101))
]
}
struct CreateNewTactileTrigger: View {
#StateObject var notesManager = NotesManager() // <-- here
var body: some View {
VStack {
ForEach($notesManager.trigArray) { $note in // <-- here
TactileTrigger(label: "\(note.trigNumber.description): [\(note.noteValue?.rawValue ?? "T")]",
bindingXY: $note.XY)
.frame(width: 25, height: 25)
VStack {
Text("\(note.trigNumber)")
Text("\(note.trigType.rawValue)")
Text("\(note.noteValue?.rawValue ?? "—")")
Text("X: \(note.XY.x)")
Text("Y: \(note.XY.y)")
}
.font(.caption)
.foregroundColor(.red) // <-- here for testing
.offset(x: 25, y: 25)
}
}
}
}

SwiftUI Using Core Data for a calculation in a Class

In my project I have a Core Data entity called "Course" which contains the following attributes:-
name (String)
tee (String)
hole1no (Int16)
hole1index (Int16)
hole1par (Int16)
I use a fetch request and select the record required which is then used in variable calculations and text fields in my views.
I then have set up a "class" view to create ObsevableObject using #Published to enable certain data to be used across multiple views.
The issue I have is in my class setup I need to use a combination of data from the Core data and data entered in the views to calculate variables which are needed in multiple views.
Below is a copy of my code, in the ScoreManager class code I get the error message
"Class ScoreManager has no initializers" which disappears if I remove any reference to the Course data so I presume that is what it is referring to in the error.
Any advice would be greatly appreciated, this is my first complex project so I may well be missing something in my knowledge. If possible could you update my code so I can see where the changes are to assist my learning.
Thanks in advance.
This is the code that fetches the data from Core Data
import SwiftUI
import CoreData
struct Score4PickCourse: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Course.entity(), sortDescriptors:[NSSortDescriptor(keyPath: \Course.name, ascending: false)]) var courses: FetchedResults<Course>
#State private var showingAddScreen = false
var body: some View {
List {
ForEach(courses, id: \.self) { course in NavigationLink(destination: Score4SelectPlayers(course: course)) {
VStack(alignment: .leading) {
Text(course.name ?? "Unknown Course")
Text(course.tee ?? "Unknown Tee")
}
}
}
}
.navigationBarTitle("Courses")
.navigationBarItems(trailing: Button(action: {
self.showingAddScreen.toggle()
}) {
Image(systemName: "plus")
}
)
.sheet(isPresented: $showingAddScreen) {
CourseAddNew().environment(\.managedObjectContext, self.moc)
}
}
}
This is the code where you enter basic information (player name, handicap etc.
import SwiftUI
import CoreData
struct Score4SelectPlayers: View {
#Environment(\.managedObjectContext) var moc
#Environment(\.presentationMode) var presentationMode
#ObservedObject var scoreManager = ScoreManager()
#State private var date = Date()
let course: Course
var body: some View {
Form {
Section {
Text("Course Selected - \(course.name ?? "No Course Selected")")
Text("Course Hole No - \(course.hole1no)")
Text("Tee Category Selected - \(course.tee ?? "No Tee Selected")")
DatePicker("Date of Round", selection: $date, displayedComponents: .date)
.foregroundColor(Color.blue)
.font(.system(size: 17, weight: .bold, design: .rounded))
TextField("Player 1 Name", text: $scoreManager.player1)
Picker("Player 1 Handicap", selection: $scoreManager.p1handicap) {
ForEach(Array(stride(from: 0, to: 55, by: 1)), id: \.self) { index in Text("\(index)")
}
} .frame(width: 300, height: 20, alignment: .center)
.foregroundColor(Color.blue)
.font(.system(size: 17, weight: .bold, design: .rounded))
}
}
NavigationLink(destination: Score4Hole1(scoreManager: scoreManager, course: course)) {
Text("Go To Hole 1")
}
}
}
struct Score4SelectPlayers_Previews: PreviewProvider {
static let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
static var previews: some View {
let course = Course(context: moc)
course.name = "Great Barr"
course.tee = "White Tee"
course.hole1no = 1
course.hole1par = 5
course.hole1index = 10
return NavigationView {
CourseDetailView(course: course)
}
}
}
This is the class code where I need to be able to include data from Core Data in the variable calculations
import SwiftUI
import CoreData
class ScoreManager : ObservableObject {
let course: Course
#Published var player1 = ""
#Published var p1handicap = 0
var p1h1shots: Int16 {
let p1h1hand = Int16(p1handicap)
let index = Int16(course.hole1index)
let p1h1shot = p1h1hand - index
if p1h1shot < 0 {
return 0
}
if p1h1shot >= 0 && p1h1shot < 18 {
return 1
}
if p1h1shot >= 18 && p1h1shot < 36 {
return 2
}
if p1h1shot >= 36 && p1h1shot < 54 {
return 3
}
return 0
}
}
I can't test the code without a Minimal Reproducible Example but these changes could work. I don't know where you will be using it
1st:
CoreData objects are ObservableObjects so in Score4SelectPlayers change your course to #ObservedObject var course: Course
2nd:
In your ScoreManager change p1h1shots to a func
func p1h1shots(hole1index: Int16) -> Int16 {
let p1h1hand = Int16(p1handicap)
let p1h1shot = p1h1hand - hole1index
if p1h1shot < 0 {
return 0
}
if p1h1shot >= 0 && p1h1shot < 18 {
return 1
}
if p1h1shot >= 18 && p1h1shot < 36 {
return 2
}
if p1h1shot >= 36 && p1h1shot < 54 {
return 3
}
return 0
}
and then you can use it in any View with scoreManager.p1h1shots(hole1index: course.hole1index)
3rd: You can always just put it in your model
extension Course{
func p1h1shots(p1handicap: Int16) -> Int16 {
let index = Int16(hole1index)
let p1h1shot = p1handicap - index
if p1h1shot < 0 {
return 0
}
if p1h1shot >= 0 && p1h1shot < 18 {
return 1
}
if p1h1shot >= 18 && p1h1shot < 36 {
return 2
}
if p1h1shot >= 36 && p1h1shot < 54 {
return 3
}
return 0
}
}
and then use it course.p1h1shots(p1handicap: Int16) in your View

SwiftUI's ForEach crashes when array's element is removed

I try to lay out my UI elements in rows - two elements in a row, for that I use two ForEach, one for rows and one for elements in a row. Each UI element has #Binding so I pass a structure from my model's array. I should be able to add or remove the elements dynamically and everything works except one thing - when I remove the only element from a row my app crashes with error Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444. I have read several topics on SO but I haven't found an answer.
This is how my code looks like:
struct PickerElement: Hashable {
let id: Int
let title: String
let value: Int
}
struct CellModel: Hashable {
var element: PickerElement?
var error: String?
}
struct PickerButton: View {
#Binding var value: CellModel?
#Binding var error: String?
(...)
}
class MyModel: ObservableObject {
#Published var counter = 0
#Published var cellModels = [CellModel]()
private var cancellables = Set<AnyCancellable>()
$counter.sink { value in
let diff = value - self.cellModels.count
if diff > 0 {
self.cellModels.append(contentsOf:
Array(repeating: CellModel(), count: diff)
)
} else if diff < 0 {
self.cellModels = Array(
self.cellModels.prefix(value)
)
}
}.store(in: &cancellables)
}
struct MyView: View {
#ObservedObject var model: MyModel
var body: some View {
VStack(spacing: 8) {
layOutElements()
}
}
#ViewBuilder
func layOutElements() -> some View {
let elementsCount = model.cellModels.count
if elementsCount > 0 {
VStack {
HStack {
Spacer()
Text("Some title").font(.caption)
Spacer()
}.padding()
// count number of rows
let rowsCount = Int(ceil(Double(elementsCount) / 2.0))
// lay out rows
ForEach(0 ..< rowsCount, id: \.self) { rowIndex in
layOutRow(rowIndex: rowIndex,
elementsCount: elementsCount,
rowsCount: rowsCount)
}
}
}
}
#ViewBuilder
private func layOutRow(rowIndex: Int, elementsCount: Int, rowsCount: Int) -> some View {
HStack(alignment: .top, spacing: 8) {
let firstCellInRowIndex = rowIndex * 2
let lastCellInRowIndex = min(elementsCount - 1, firstCellInRowIndex + 1)
ForEach(firstCellInRowIndex ... lastCellInRowIndex, id: \.self) { elementIndex in
PickerButton(value: $model.cellModels[elementIndex].element, // <--- *1
error: $model.cellModels[elementIndex].error) // <--- *2
}
// *1 , *2 - if I changed the lines and pass dummy bindings (not array's elements) there, the code would work without any glitches
if rowIndex == rowsCount - 1 && !elementsCount.isMultiple(of: 2) {
Spacer()
.frame(minWidth: 0, maxWidth: .infinity)
}
}
}
If I change a value of #Published var counter = 0 everything works properly, views are added and removed, but while decrementing if SwiftUI tries to remove the last remaining element from a row the app crashes. As I have commented in the code above, if I don't bind PickerButton to the structures from my model's array, the app doesn't crash. How to fix this issue? (I need to use indexes because I have to count rows and cells in a row)
The crash happens when elementsCount in layOutElements() becomes 0. Then layOutElements() doesn't return a View, which should be impossible. You can add an else to your if statement that returns EmptyView() or some placeholder.
Other than that, in my experience ForEach with dynamic ranges is a mess. If you need indexes you can use Array.enumerated() which has given me better results.

SwiftUI Random but unique elements

I have a simple array of 3 fonts
var fonts = ["Arial", "System", "Helvetica"]
let randomFont: String
so the user is asked to guess one of the 3
VStack(spacing: 30) {
Text("Which font is \(randomFont)?")
so this correctly displays one of the 3 elements.
Each has to use a unique element of the fonts array and...if is the same font as per randomFont the counter should be increased by one.
Here is an hardcoded example:
Button(action: { self.score += 1 }) {
Text("\(randomWord)").font(.custom("Helvetica", size: 40))
}
Button(action: { self.score += 0 }) {
Text("\(randomWord)").font(.custom("Arial", size: 40))
}
Button(action: { self.score += 0 }) {
Text("\(randomWord)").font(.system(size: 40))
}
I need help to randomise the fonts but all 3 have to be used. Additionally I need to compare the random font selected by the user with the font displayed in the question. if is the same (right answer) a point is added.
so for instance
randomFont is "Arial"
the buttons will display 3 words in a random font (e.g.randomFont2 variable) (Arial, Helvetica or system all have to be used and no duplicates) if the user clicks the button with the Arial font +1 point is added to the counter.
Here's a complete standalone example. I used 3 very distinct fonts (Courier, System, and Papyrus) for testing purposes.
fonts.shuffle() is used to randomize the fonts.
The three buttons only differ in the font index they are using and looking for, so I've used a private function to contruct the buttons to avoid repetition.
The randomly chosen randomFontIndex uses Int.random(in: 0..<3) to choose a number that is 0, 1, or 2. To add more fonts, just add more font names to the fonts array. Make sure to select valid font names though!
import SwiftUI
struct ContentView: View {
let words = ["birds", "meadow", "butterfly", "flowers"]
#State private var randomFontIndex = 0
#State private var randomFont = "Courier"
#State private var fonts = ["Courier", "System", "Papyrus"]
#State private var score = 0
#State private var randomWord = ""
var body: some View {
VStack(spacing: 30) {
Text("Score: \(score)")
Text("Which font is \(randomFont)?")
buttonFromNumber(0)
buttonFromNumber(1)
buttonFromNumber(2)
}.onAppear { self.newGame() }
}
private func fontFromName(name: String) -> Font {
switch name {
case "System":
return Font.system(size: 40)
default:
return Font.custom(name, size: 40)
}
}
private func buttonFromNumber(_ number: Int) -> some View {
Button(action: {
if number == self.randomFontIndex {
self.score += 1
} else {
self.score -= 1
}
self.newGame()
}) {
Text("\(randomWord)").font(fontFromName(name: fonts[number]))
}
}
private func newGame() {
fonts.shuffle()
randomFontIndex = .random(in: 0..<3)
randomFont = fonts[randomFontIndex]
randomWord = words.randomElement()!
}
}