Are there maximum limits to VStack? - swiftui
I started with a clean project and added 5 buttons and 5 spacers in a VStack and all is good. When I add the 6th spacer at the bottom, the code suddenly won't compile with the error: "Ambiguous reference to member 'buildBlock()'".
What is causing this error? Is this a bug related to SwiftUI? Or is it a feature? It's not the first time I notice that VStack or HStack is limited in the number of entries, is there some documentation around this?
Not exactly confidence inspiring, should I switch back to UIKit?
SwiftUI uses ViewBuilder to construct the views that make up many SwiftUI views, like VStack, HStack, List, etc. If you take a look at the ViewBuilder documentation, you'll see that the buildBlock function has many copies, each with a different amount of views as arguments. The function with the most amount of views only takes in 10 views which is why you are seeing the limitation that you observed. A way to work around this is by using Groups:
VStack {
Group {
Text("Placeholder 0")
Text("Placeholder 1")
Text("Placeholder 2")
Text("Placeholder 3")
Text("Placeholder 4")
Text("Placeholder 5")
Text("Placeholder 6")
Text("Placeholder 7")
Text("Placeholder 8")
Text("Placeholder 9")
}
Group {
Text("Other Placeholder 10")
Text("Other Placeholder 11")
Text("Other Placeholder 12")
Text("Other Placeholder 13")
Text("Other Placeholder 14")
Text("Other Placeholder 15")
Text("Other Placeholder 16")
Text("Other Placeholder 17")
Text("Other Placeholder 18")
Text("Other Placeholder 19")
}
}
Although if you want 20 views that are really similar to each other, it is encouraged to use something like a ForEach to avoid making your views too bloated. The above workaround should only be used if the >10 views are truly unique. Even then, a more SwiftUI-y method would be to split up these views into more smaller views:
VStack {
SingleDigitPlaceholders()
TeensPlaceholders()
}
struct SingleDigitPlaceholders: View {
var body: some View {
ForEach(0..<10) { i in
Text("Placeholder \(i)")
}
}
}
struct TeensPlaceholders: View {
var body: some View {
ForEach(10..<20) { i in
Text("Other Placeholder \(i)")
}
}
}
Of course, in this specific example, you can just have the two ForEachs in the original view, but in more complex cases, the point still stands. For example, in a form with many elements (e.g. in a job application form: first name, last name, address, phone number text fields, education dropdown menus, date fields, etc.) you can still split up one view into smaller components (in the job application example - a personal information view, an educational information view, etc.).
You can have at most 10 children in your VStack (and ZStack, HStack, and so forth). This is strictly related to their implementation and to the implementation of the #ViewBuilder closures in general. Look at the interfaces here below (you can find them through xCode, I slightly simplified them in order to be more readable):
public struct ViewBuilder {
/// Builds an empty view from an block containing no statements, `{ }`.
public static func buildBlock() -> EmptyView
/// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) through
/// unmodified.
public static func buildBlock<Content>(_ content: Content) -> Content where Content : View
}
extension ViewBuilder {
public static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> TupleView<(C0, C1)> where C0 : View, C1 : View
}
extension ViewBuilder {
public static func buildBlock<C0, C1, C2>(_ c0: C0, _ c1: C1, _ c2: C2) -> TupleView<(C0, C1, C2)> where C0 : View, C1 : View, C2 : View
}
extension ViewBuilder {
public static func buildBlock<C0, C1, C2, C3>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3) -> TupleView<(C0, C1, C2, C3)> where C0 : View, C1 : View, C2 : View, C3 : View
}
extension ViewBuilder {
public static func buildBlock<C0, C1, C2, C3, C4>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4) -> TupleView<(C0, C1, C2, C3, C4)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View
}
extension ViewBuilder {
public static func buildBlock<C0, C1, C2, C3, C4, C5>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5) -> TupleView<(C0, C1, C2, C3, C4, C5)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View, C5 : View
}
extension ViewBuilder {
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6) -> TupleView<(C0, C1, C2, C3, C4, C5, C6)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View, C5 : View, C6 : View
}
extension ViewBuilder {
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View, C5 : View, C6 : View, C7 : View
}
extension ViewBuilder {
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View, C5 : View, C6 : View, C7 : View, C8 : View
}
extension ViewBuilder {
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View, C5 : View, C6 : View, C7 : View, C8 : View, C9 : View
}
As you can see you can build those kind of views with at most 10 children. Each buildBlock method takes an exact number of views as parameters. This is because #ViewBuilder closures, at least at the moment, don't support variadic arguments.
A workaround can be:
struct ContentView: View {
var body: some View {
VStack {
Group {
//10 views here
}
Group {
//10 views here
}
}
}
}
I have 6 views after added the Group around my views, I'm still getting the Extra argument in call error.
So this is what I did to fix the error.
struct ContentView: View {
var body: some View {
VStack {
Group {
//3 views here
}
Group {
//3 views here
}
}
}
}
Here is a ViewBuilder extension that supports up to an additional 20 views.
import SwiftUI
extension ViewBuilder {
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10>(
_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9, _ c10: C10
) -> TupleView<
(
Group<TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)>>,
Group<TupleView<(C10)>>
)
>
where C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View, C8: View, C9: View, C10: View {
TupleView((
Group { TupleView((c0, c1, c2, c3, c4, c5, c6, c7, c8, c9)) },
Group { TupleView((c10)) }
))
}
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11>(
_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9, _ c10: C10, _ c11: C11
) -> TupleView<
(
Group<TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)>>,
Group<TupleView<(C10, C11)>>
)
>
where C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View, C8: View, C9: View, C10: View, C11: View {
TupleView((
Group { TupleView((c0, c1, c2, c3, c4, c5, c6, c7, c8, c9)) },
Group { TupleView((c10, c11)) }
))
}
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12>(
_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9, _ c10: C10, _ c11: C11, _ c12: C12
) -> TupleView<
(
Group<TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)>>,
Group<TupleView<(C10, C11, C12)>>
)
>
where C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View, C8: View, C9: View, C10: View, C11: View, C12: View {
TupleView((
Group { TupleView((c0, c1, c2, c3, c4, c5, c6, c7, c8, c9)) },
Group { TupleView((c10, c11, c12)) }
))
}
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13>(
_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9, _ c10: C10, _ c11: C11, _ c12: C12, _ c13: C13
) -> TupleView<
(
Group<TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)>>,
Group<TupleView<(C10, C11, C12, C13)>>
)
>
where C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View, C8: View, C9: View, C10: View, C11: View, C12: View, C13: View {
TupleView((
Group { TupleView((c0, c1, c2, c3, c4, c5, c6, c7, c8, c9)) },
Group { TupleView((c10, c11, c12, c13)) }
))
}
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14>(
_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9, _ c10: C10, _ c11: C11, _ c12: C12, _ c13: C13, _ c14: C14
) -> TupleView<
(
Group<TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)>>,
Group<TupleView<(C10, C11, C12, C13, C14)>>
)
>
where C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View, C8: View, C9: View, C10: View, C11: View, C12: View, C13: View, C14: View {
TupleView((
Group { TupleView((c0, c1, c2, c3, c4, c5, c6, c7, c8, c9)) },
Group { TupleView((c10, c11, c12, c13, c14)) }
))
}
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15>(
_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9, _ c10: C10, _ c11: C11, _ c12: C12, _ c13: C13, _ c14: C14, _ c15: C15
) -> TupleView<
(
Group<TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)>>,
Group<TupleView<(C10, C11, C12, C13, C14, C15)>>
)
>
where C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View, C8: View, C9: View, C10: View, C11: View, C12: View, C13: View, C14: View, C15: View {
TupleView((
Group { TupleView((c0, c1, c2, c3, c4, c5, c6, c7, c8, c9)) },
Group { TupleView((c10, c11, c12, c13, c14, c15)) }
))
}
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15, C16>(
_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9, _ c10: C10, _ c11: C11, _ c12: C12, _ c13: C13, _ c14: C14, _ c15: C15, _ c16: C16
) -> TupleView<
(
Group<TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)>>,
Group<TupleView<(C10, C11, C12, C13, C14, C15, C16)>>
)
>
where C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View, C8: View, C9: View, C10: View, C11: View, C12: View, C13: View, C14: View, C15: View, C16: View {
TupleView((
Group { TupleView((c0, c1, c2, c3, c4, c5, c6, c7, c8, c9)) },
Group { TupleView((c10, c11, c12, c13, c14, c15, c16)) }
))
}
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15, C16, C17>(
_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9, _ c10: C10, _ c11: C11, _ c12: C12, _ c13: C13, _ c14: C14, _ c15: C15, _ c16: C16, _ c17: C17
) -> TupleView<
(
Group<TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)>>,
Group<TupleView<(C10, C11, C12, C13, C14, C15, C16, C17)>>
)
>
where C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View, C8: View, C9: View, C10: View, C11: View, C12: View, C13: View, C14: View, C15: View, C16: View, C17: View {
TupleView((
Group { TupleView((c0, c1, c2, c3, c4, c5, c6, c7, c8, c9)) },
Group { TupleView((c10, c11, c12, c13, c14, c15, c16, c17)) }
))
}
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15, C16, C17, C18>(
_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9, _ c10: C10, _ c11: C11, _ c12: C12, _ c13: C13, _ c14: C14, _ c15: C15, _ c16: C16, _ c17: C17, _ c18: C18
) -> TupleView<
(
Group<TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)>>,
Group<TupleView<(C10, C11, C12, C13, C14, C15, C16, C17, C18)>>
)
>
where C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View, C8: View, C9: View, C10: View, C11: View, C12: View, C13: View, C14: View, C15: View, C16: View, C17: View, C18: View {
TupleView((
Group { TupleView((c0, c1, c2, c3, c4, c5, c6, c7, c8, c9)) },
Group { TupleView((c10, c11, c12, c13, c14, c15, c16, c17, c18)) }
))
}
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15, C16, C17, C18, C19>(
_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9, _ c10: C10, _ c11: C11, _ c12: C12, _ c13: C13, _ c14: C14, _ c15: C15, _ c16: C16, _ c17: C17, _ c18: C18, _ c19: C19
) -> TupleView<
(
Group<TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)>>,
Group<TupleView<(C10, C11, C12, C13, C14, C15, C16, C17, C18, C19)>>
)
>
where C0: View, C1: View, C2: View, C3: View, C4: View, C5: View, C6: View, C7: View, C8: View, C9: View, C10: View, C11: View, C12: View, C13: View, C14: View, C15: View, C16: View, C17: View, C18: View, C19: View {
TupleView((
Group { TupleView((c0, c1, c2, c3, c4, c5, c6, c7, c8, c9)) },
Group { TupleView((c10, c11, c12, c13, c14, c15, c16, c17, c18, c19)) }
))
}
}
Related
How do I add Multiple AI Difficulties to my func computerPlay() in my UIViewController? I am creating a tic tac toe game in UIKit
How do I add Multiple AI Difficulties to my func computerPlay() in my UIViewController? I am creating a tic tac toe game in UIKit I know how to do it in swiftui from another project. I don't know how to change their names to the correct code in my project. I have tried and kept failing. Right now when you play against the computer and try to lose, majority of the time you will get a draw or a force win. Please help. // // xoGame.swift // TicBones // // Created by Carissa Richardson on 12/8/22. // import UIKit import SwiftUI class xoGame: UIViewController { #IBOutlet weak var playerScore3: UILabel! #IBOutlet weak var a2: UIImageView! #IBOutlet weak var a3: UIImageView! #IBOutlet weak var b1: UIImageView! #IBOutlet weak var b2: UIImageView! #IBOutlet weak var b3: UIImageView! #IBOutlet weak var c1: UIImageView! #IBOutlet weak var c2: UIImageView! #IBOutlet weak var c3: UIImageView! #IBOutlet weak var backgroundLabel: UIImageView! #IBOutlet weak var a1: UIImageView! var gameP1 = "o" var playerChoices: [Box2] = [] var computerChoices: [Box2] = [] override func viewDidLoad() { super.viewDidLoad() createTap(on: a1, type: .one) createTap(on: a2, type: .two) createTap(on: a3, type: .three) createTap(on: b1, type: .four) createTap(on: b2, type: .five) createTap(on: b3, type: .six) createTap(on: c1, type: .seven) createTap(on: c2, type: .eight) createTap(on: c3, type: .nine) } #IBOutlet weak var computerScore3: UILabel! func createTap(on imageView: UIImageView, type box: Box2){ let tap = UITapGestureRecognizer(target: self, action: #selector(self.boxClicked(_:))) tap.name = box.rawValue imageView.isUserInteractionEnabled = true imageView.addGestureRecognizer(tap) } #objc func boxClicked(_ sender: UITapGestureRecognizer){ // print("Box: \(sender.name) was clicked") let selectedBox = getBox(from: sender.name ?? "") makeChoice(selectedBox) playerChoices.append(Box2(rawValue: sender.name!)!) checkIfWon() DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.computerPlay() } } func computerPlay(){ var availableSpaces = [UIImageView]() var availableBoxes = [Box2]() for name in Box2.allCases { let box = getBox(from: name.rawValue) if box.image == nil { availableSpaces.append(box) availableBoxes.append(name) } } guard availableBoxes.count > 0 else { return } // THIS IS WERE I NEED HELP! // If AI can win, than win //If AI cant win, than block // If AI cant block, then take middle source //If AI cant take middle square, take random available square let randIndex = Int.random(in: 0 ..< availableSpaces.count) makeChoice(availableSpaces[randIndex]) computerChoices.append(availableBoxes[randIndex]) checkIfWon() } func makeChoice(_ selectedBox: UIImageView) { guard selectedBox.image == nil else { return } if gameP1 == "x" { selectedBox.image = #imageLiteral(resourceName: "O") gameP1 = "o" } else { selectedBox.image = #imageLiteral(resourceName: "x") gameP1 = "x" } } func checkIfWon() { var correct = [[Box2]]() let firstRow: [Box2] = [.one, .two, .three] let secondRow: [Box2] = [.four, .five, .six] let thirdRow: [Box2] = [.seven, .eight, .nine] let firstCol: [Box2] = [.one, .four, .seven] let secondCol: [Box2] = [.two, .five, .six] let thirdCol: [Box2] = [.three, .six, .nine] let backwardSlash: [Box2] = [.one, .five, .nine] let forwardSlash: [Box2] = [.three, .five, .seven] correct.append(firstRow) correct.append(secondRow) correct.append(thirdRow) correct.append(firstCol) correct.append(secondCol) correct.append(thirdCol) correct.append(backwardSlash) correct.append(forwardSlash) for valid in correct { let userMatch = playerChoices.filter { valid.contains($0) }.count let computerMatch = computerChoices.filter { valid.contains($0) }.count if userMatch == valid.count { playerScore3.text = String((Int(playerScore3.text ?? "0") ?? 0) + 1) resetGame() break } else if computerMatch == valid.count { computerScore3.text = String((Int(computerScore3.text ?? "0") ?? 0) + 1) resetGame() break } else if computerChoices.count + playerChoices.count == 9 { resetGame() break } } } func resetGame() { for name in Box2.allCases { let box = getBox(from: name.rawValue) box.image = nil } gameP1 = "o" playerChoices = [] computerChoices = [] } func getBox(from name: String) -> UIImageView { let box = Box2(rawValue: name) ?? .one switch box { case .one: return a1 case .two: return a2 case .three: return a3 case .four: return b1 case .five: return b2 case .six: return b3 case .seven: return c1 case .eight: return c2 case .nine: return c3 } } #IBAction func dice(_ sender: Any) { backgroundLabel.image = [ #imageLiteral(resourceName: "Screen Shot 2022-08-10 at 10.52.27 PM"), #imageLiteral(resourceName: "Screen Shot 2022-08-10 at 12.18.19 AM"), #imageLiteral(resourceName: "Screen Shot 2022-07-04 at 3.43.07 PM"), #imageLiteral(resourceName: "Screen Shot 2022-08-10 at 10.53.00 PM"), #imageLiteral(resourceName: "Screen Shot 2022-08-10 at 11.56.06 PM"), #imageLiteral(resourceName: "Screen Shot 2022-07-04 at 3.11.02 PM"), #imageLiteral(resourceName: "Ice Cream In Paper Cut Background"), #imageLiteral(resourceName: "Screen Shot 2022-07-04 at 3.11.02 PM"), #imageLiteral(resourceName: "Screen Shot 2022-08-10 at 12.28.13 AM"), #imageLiteral(resourceName: "Screen Shot 2022-08-10 at 10.52.03 PM"), #imageLiteral(resourceName: "IMG_9062"), #imageLiteral(resourceName: "IMG_9064"), #imageLiteral(resourceName: "IMG_9061"), #imageLiteral(resourceName: "IMG_9063"), #imageLiteral(resourceName: "IMG_9065"), #imageLiteral(resourceName: "Screen Shot 2022-07-04 at 3.10.05 PM"), #imageLiteral(resourceName: "backPinkbirth"), #imageLiteral(resourceName: "skybackground"), ][Int.random(in: 0...17)] } #IBAction func refresh(_ sender: Any) { self.dismiss(animated: true, completion: nil) } enum Box2: String, CaseIterable{ case one, two, three, four, five, six, seven, eight, nine } }
I am trying to create a base function to use on multiple randomisation in a program (Beginner level)
Dice Roller For example on this app (I simply created for my character creation needs for D&D), when you press "Roll", the program rolls 3 times "d6 (6 sided dice)" and adds them up, and adds the sum to a the corresponding "Stat" Array, it does this 3 times, and then takes the highest value from the array as a result. My noob function code here is here; func rollStr() { strArray.removeAll() let r1 = Int.random(in: 1...6) let r2 = Int.random(in: 1...6) let r3 = Int.random(in: 1...6) let r4 = Int.random(in: 1...6) let r5 = Int.random(in: 1...6) let r6 = Int.random(in: 1...6) let r7 = Int.random(in: 1...6) let r8 = Int.random(in: 1...6) let r9 = Int.random(in: 1...6) strArray.append(r1 + r2 + r3) strArray.append(r4 + r5 + r6) strArray.append(r7 + r8 + r9) strStat = strArray.max()! } I didn't manage to declare only 3 values and roll it 3 times, so I decided to do it with 9 variables. Anyway as you see above there are "strArray" and "strStat" in this function. I had to write 6 copies of this function for each different stat. I tried to use an editable function like; func rollStat(arrayNumber: Int, statNumber: Int) { var allArrays = [strArray, dexArray, consArray, intArray, wisArray, chaArray] var allStats = [strStat, dexStat, consStat, intStat, wisStat, chaStat] allArrays[arrayNumber].removeAll() let r1 = Int.random(in: 1...6) let r2 = Int.random(in: 1...6) let r3 = Int.random(in: 1...6) let r4 = Int.random(in: 1...6) let r5 = Int.random(in: 1...6) let r6 = Int.random(in: 1...6) let r7 = Int.random(in: 1...6) let r8 = Int.random(in: 1...6) let r9 = Int.random(in: 1...6) allArrays[arrayNumber].append(r1 + r2 + r3) allArrays[arrayNumber].append(r4 + r5 + r6) allArrays[arrayNumber].append(r7 + r8 + r9) allStats[statNumber] = allArrays[arrayNumber].max()! } and wanted to call the function as "rollStat(arrayNumber: 1, statNumber: 1)" to use one base function to call what value I need instead of using multiple copies, but I couldn't manage to make it work. What am I doing wrong here? As I said I achieved what I needed, the program works but if possible I want to know the tricks of doing it with only one function. I will paste all the code below, if you wanted to know anything about the previous lines. Thanks in advance. struct ContentView: View { // MARK: PROPERTY #State private var strArray:[Int] = [0,0,0] #State private var dexArray:[Int] = [0,0,0] #State private var consArray:[Int] = [0,0,0] #State private var intArray:[Int] = [0,0,0] #State private var wisArray:[Int] = [0,0,0] #State private var chaArray:[Int] = [0,0,0] #State private var strStat = 0 #State private var dexStat = 0 #State private var consStat = 0 #State private var intStat = 0 #State private var wisStat = 0 #State private var chaStat = 0 // MARK: METHOD func rollStr() { strArray.removeAll() let r1 = Int.random(in: 1...6) let r2 = Int.random(in: 1...6) let r3 = Int.random(in: 1...6) let r4 = Int.random(in: 1...6) let r5 = Int.random(in: 1...6) let r6 = Int.random(in: 1...6) let r7 = Int.random(in: 1...6) let r8 = Int.random(in: 1...6) let r9 = Int.random(in: 1...6) strArray.append(r1 + r2 + r3) strArray.append(r4 + r5 + r6) strArray.append(r7 + r8 + r9) strStat = strArray.max()! } func rollDex() { dexArray.removeAll() let r1 = Int.random(in: 1...6) let r2 = Int.random(in: 1...6) let r3 = Int.random(in: 1...6) let r4 = Int.random(in: 1...6) let r5 = Int.random(in: 1...6) let r6 = Int.random(in: 1...6) let r7 = Int.random(in: 1...6) let r8 = Int.random(in: 1...6) let r9 = Int.random(in: 1...6) dexArray.append(r1 + r2 + r3) dexArray.append(r4 + r5 + r6) dexArray.append(r7 + r8 + r9) dexStat = dexArray.max()! } func rollCons() { consArray.removeAll() let r1 = Int.random(in: 1...6) let r2 = Int.random(in: 1...6) let r3 = Int.random(in: 1...6) let r4 = Int.random(in: 1...6) let r5 = Int.random(in: 1...6) let r6 = Int.random(in: 1...6) let r7 = Int.random(in: 1...6) let r8 = Int.random(in: 1...6) let r9 = Int.random(in: 1...6) consArray.append(r1 + r2 + r3) consArray.append(r4 + r5 + r6) consArray.append(r7 + r8 + r9) consStat = consArray.max()! } func rollInt() { intArray.removeAll() let r1 = Int.random(in: 1...6) let r2 = Int.random(in: 1...6) let r3 = Int.random(in: 1...6) let r4 = Int.random(in: 1...6) let r5 = Int.random(in: 1...6) let r6 = Int.random(in: 1...6) let r7 = Int.random(in: 1...6) let r8 = Int.random(in: 1...6) let r9 = Int.random(in: 1...6) intArray.append(r1 + r2 + r3) intArray.append(r4 + r5 + r6) intArray.append(r7 + r8 + r9) intStat = intArray.max()! } func rollWis() { wisArray.removeAll() let r1 = Int.random(in: 1...6) let r2 = Int.random(in: 1...6) let r3 = Int.random(in: 1...6) let r4 = Int.random(in: 1...6) let r5 = Int.random(in: 1...6) let r6 = Int.random(in: 1...6) let r7 = Int.random(in: 1...6) let r8 = Int.random(in: 1...6) let r9 = Int.random(in: 1...6) wisArray.append(r1 + r2 + r3) wisArray.append(r4 + r5 + r6) wisArray.append(r7 + r8 + r9) wisStat = wisArray.max()! } func rollCha() { chaArray.removeAll() let r1 = Int.random(in: 1...6) let r2 = Int.random(in: 1...6) let r3 = Int.random(in: 1...6) let r4 = Int.random(in: 1...6) let r5 = Int.random(in: 1...6) let r6 = Int.random(in: 1...6) let r7 = Int.random(in: 1...6) let r8 = Int.random(in: 1...6) let r9 = Int.random(in: 1...6) chaArray.append(r1 + r2 + r3) chaArray.append(r4 + r5 + r6) chaArray.append(r7 + r8 + r9) chaStat = chaArray.max()! } func rollStat(arrayNumber: Int, statNumber: Int) { var allArrays = [strArray, dexArray, consArray, intArray, wisArray, chaArray] var allStats = [strStat, dexStat, consStat, intStat, wisStat, chaStat] allArrays[arrayNumber].removeAll() let r1 = Int.random(in: 1...6) let r2 = Int.random(in: 1...6) let r3 = Int.random(in: 1...6) let r4 = Int.random(in: 1...6) let r5 = Int.random(in: 1...6) let r6 = Int.random(in: 1...6) let r7 = Int.random(in: 1...6) let r8 = Int.random(in: 1...6) let r9 = Int.random(in: 1...6) allArrays[arrayNumber].append(r1 + r2 + r3) allArrays[arrayNumber].append(r4 + r5 + r6) allArrays[arrayNumber].append(r7 + r8 + r9) allStats[statNumber] = allArrays[arrayNumber].max()! } // MARK: BODY var body: some View { // MARK: STATS UI VStack { HStack(spacing: 10) { VStack(spacing: 6) { sixDiceView() sixDiceView() sixDiceView() sixDiceView() sixDiceView() sixDiceView() } //: Vstack statNames() ZStack { VStack(spacing: 16) { Text(String(strStat)) Text(String(dexStat)) Text(String(consStat)) Text(String(intStat)) Text(String(wisStat)) Text(String(chaStat)) } VStack(spacing: 6) { Capsule() .fill(Color.blue.opacity(0.2)) .frame(width: 60, height: 30) Capsule() .fill(Color.blue.opacity(0.2)) .frame(width: 60, height: 30) Capsule() .fill(Color.blue.opacity(0.2)) .frame(width: 60, height: 30) Capsule() .fill(Color.blue.opacity(0.2)) .frame(width: 60, height: 30) Capsule() .fill(Color.blue.opacity(0.2)) .frame(width: 60, height: 30) Capsule() .fill(Color.blue.opacity(0.2)) .frame(width: 60, height: 30) } //: VStack } //: ZStack ZStack { rollsCapsule() VStack(alignment: .leading, spacing: 16) { Text("\(strArray[0]),\(strArray[1]),\(strArray[2])") Text("\(dexArray[0]),\(dexArray[1]),\(dexArray[2])") Text("\(consArray[0]),\(consArray[1]),\(consArray[2])") Text("\(intArray[0]),\(intArray[1]),\(intArray[2])") Text("\(wisArray[0]),\(wisArray[1]),\(wisArray[2])") Text("\(chaArray[0]),\(chaArray[1]),\(chaArray[2])") } //: VStack } //: ZStack } //: HStack // MARK: BUTTON ROLL Button(action: { rollStr() rollDex() rollCons() rollInt() rollWis() rollCha() }) { rollButton() } //: BUTTON } //: VStack } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } }
Swift functions can return multiple values using a tuple. If you create a general roll function to return the array and maximum value, you can just call it and assign the result to your variables: func roll() -> (array: [Int], max: Int) { var result = [Int]() for _ in 1...3 { var sum = 0 for _ in 1...3 { sum += Int.random(in: 1...6) } result.append(sum) } return (result, result.max()!) } Then you'd call it like this: (strArray, strStat) = roll() (dexArray, dexStat) = roll() Define a RollData struct Your array and stat shouldn't be two different #State vars since they are related. You should combine them into a struct. struct RollData { let array: [Int] let stat: Int } func roll() -> RollData { var result = [Int]() for _ in 1...3 { var sum = 0 for _ in 1...3 { sum += Int.random(in: 1...6) } result.append(sum) } return RollData(array: result, stat: result.max()!) } Then you'd declare your #State vars like this: #State private var strRoll = roll() #State private var dexRoll = roll() and roll like this: strRoll = roll() dexRoll = roll() and access the data like this: Text(String(strRoll.stat)) Text("\(strRoll.array[0]),\(strRoll.array[1]),\(strRoll.array[2])") Complete Example This is a fun project to play with. I made some additional changes that I thought you might like to see. Notes: I created a DieType enum which has a value for each die type. I made it of type Int so that we could use it as in index into an array of all of the dies. I made it CaseIterable so that we can get an array of all types with DieType.allCases. I made it CustomStringConvertible so the type would nicely print its name as returned by the computed property description. I made it Identifiable so that we can use it SwiftUI like this: ForEach(DieType.allCases) { die in. I created an AllDice struct which holds all of the dice. Now we only need one #State dice = AllDice() for the app. It has properties to roll all of the dice, roll a single die by type, get the stat and array for a die. The main loop for the app is a VStack with a row for each die type. There are no hardcoded 6's for number of die types. By defining a new die type, or removing one, the code adapts automatically. import SwiftUI struct RollData { let array: [Int] let stat: Int } func roll() -> RollData { var result = [Int]() for _ in 1...3 { var sum = 0 for _ in 1...3 { sum += Int.random(in: 1...6) } result.append(sum) } return RollData(array: result, stat: result.max()!) } enum DieType: Int, CaseIterable, CustomStringConvertible, Identifiable { case strength = 0, dexterity, constitution, intelligence, wisdom, charisma var id: Int { self.rawValue } var description: String { switch self { case .strength: return "Strength" case .dexterity: return "Dexterity" case .constitution: return "Constitution" case .intelligence: return "Intelligence" case .wisdom: return "Wisdom" case .charisma: return "Charisma" } } } struct AllDice { private var dice: [RollData] = (0..<DieType.allCases.count).map { _ in roll() } func array(die: DieType) -> [Int] { dice[die.rawValue].array } func stat(die: DieType) -> Int { dice[die.rawValue].stat } mutating func rollDie(die: DieType) { dice[die.rawValue] = roll() } mutating func rollAll() { for die in DieType.allCases { rollDie(die: die) } } } struct ContentView: View { #State private var dice = AllDice() var body: some View { // MARK: STATS UI VStack { ForEach(DieType.allCases) { die in let array = dice.array(die: die) HStack(spacing: 10) { Capsule() .fill(Color.blue.opacity(0.2)) .frame(width: 80, height: 30) .overlay ( HStack { Text("3 x") Image(systemName: "dice") .foregroundColor(.red) } ) Text(String(describing: die)) .frame(width: 100) Capsule() .fill(Color.blue.opacity(0.2)) .frame(width: 50, height: 30) .overlay ( Text(String(describing: dice.stat(die: die))) ) Capsule() .fill(Color.blue.opacity(0.2)) .frame(width: 100, height: 30) .overlay ( Text("\(array[0]), \(array[1]), \(array[2])") ) } //: HStack } //: ForEach // MARK: BUTTON ROLL Button(action: { dice.rollAll() }) { Capsule() .fill(Color.blue.opacity(0.2)) .frame(width: 130, height: 30) .overlay ( HStack { Image(systemName: "dice") Text("Roll Stats") } .foregroundColor(.red) ) } //: BUTTON } //: VStack } }
SwiftUI : Is there a way to force a Shape to rotate with the phone?
SwiftUI : Is there a way to force a Shape (a View of type "Shape") to rotate with the phone (as opposed to rotate automatically) so that it keeps its original orientation with respect to the screen of the phone? // Symbol House struct House: Shape { let dx: CGFloat let dy: CGFloat func path(in r: CGRect) -> Path { var path = Path() var p0 = point(in: r, from: CGPoint(x: r.minX, y: r.minY)) p0 = shift(p: p0, dx: dx, dy: dy) var p1 = point(in: r, from: CGPoint(x: r.minX, y: r.maxY/2)) p1 = shift(p: p1, dx: dx, dy: dy) var p2 = point(in: r, from: CGPoint(x: r.maxX/2 , y: r.maxY)) p2 = shift(p: p2, dx: dx, dy: dy) var p3 = point(in: r, from: CGPoint(x: r.maxX, y: r.maxY/2)) p3 = shift(p: p3, dx: dx, dy: dy) var p4 = point(in: r, from: CGPoint(x: r.maxX, y: r.minY)) p4 = shift(p: p4, dx: dx, dy: dy) path.move(to: p0) path.addLine(to: p1) path.addLine(to: p2) path.addLine(to: p3) path.addLine(to: p4) path.addLine(to: p0) return path } }
First, you need the current orientation... struct DeviceRotationViewModifier: ViewModifier { let action: (UIDeviceOrientation) -> Void func body(content: Content) -> some View { content .onAppear() .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in action(UIDevice.current.orientation) } } } extension View { func onRotate(perform action: #escaping (UIDeviceOrientation) -> Void) -> some View { self.modifier(DeviceRotationViewModifier(action: action)) } } Now we can use the .rotationEffect() - modifier. rotationEffect(_:anchor:) Rotates this view’s rendered output around the specified point. https://developer.apple.com/documentation/swiftui/view/rotationeffect(_:anchor:) The apple example: Text("Rotation by passing an angle in degrees") .rotationEffect(.degrees(22)) .border(Color.gray) ... #State private var orientation = UIDeviceOrientation.unknown ... var body: some View { Group { if orientation.isPortrait{ Shape().rotationEffect(.degrees(0)) } else if orientation.isLandscape{ Shape().rotationEffect(.degrees(90)) } }.onRotate{ newOrientation in orientation = newOrientation } } For more rotation features take a look at the link above, there are more rotation and transformation topics :-)
SwiftUI: How to call a function to populate a LazyVGrid
I'm working on some code in SwiftUI (learning as I go) where I'm constructing a vertical grid of items (This is heavily simplified for the purposes of this question): let col1 = GridItem(alignment: .leading) let col2 = GridItem(alignment: .trailing) LazyVGrid(columns: [col1, col2]) { Text("C1") Text("C1") Text("C2") Text("C2") } So I get something like this: +----+----+ | C1 | C1 | +----+----+ | C2 | C2 | +----+----+ Now in my code I'm doing some other stuff so I'd like to extract a function so my code looks something like this: let col1 = GridItem(alignment: .leading) let col2 = GridItem(alignment: .trailing) LazyVGrid(columns: [col1, col2]) { row("C1") row("C2") } func row(text: String) -> ???? { Text(text) Text(text) } But I'm finding it difficult to see how to do it. Does the function return an array? or is there some aspect of Swift's builders I can use here? I tried an array but LazyVGrid's build didn't like it.
Research the #ViewBuilder attribute. This makes the function behave like the closure you're passing to LazyVGrid and many of the SwiftUI Views. #ViewBuilder func row(text: String) -> some View { Text(text) Text(text) }
How can I access to TupleView through ViewBuilder in SwiftUI?
I am trying to get access to my TupleView which my ViewBuilder use it to build the content, then I am trying to count it, here I made my codes until this point, I need help to find the count of my TupleView, right now for unknown reason it returns 0, which I was expecting 5! thanks for help. import SwiftUI struct ContentView: View { var body: some View { ModelView(content: { Text("Hello, world! 1") .padding() Text("Hello, world! 2") .padding() Text("Hello, world! 3") .padding() Text("Hello, world! 4") .padding() Text("Hello, world! 5") .padding() }) } } struct ModelView<Content: View>: View { var content: () -> Content init(#ViewBuilder content: #escaping () -> Content) { self.content = content let size = Mirror(reflecting: content).children.count // <<: Here! it returns 0! which must be 5! print(size) } var body: some View { content() } }
How can I access to TupleView through ViewBuilder in SwiftUI? Explicitly to the same way as SwiftUI does. Let's see what is buildBlock of the ViewBuilder: That means that ViewBuilder has explicit function for every count of content views from 1 to 10. Like extension ViewBuilder { public static func buildBlock<C0, C1, C2, C3>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3) -> TupleView<(C0, C1, C2, C3)> where C0 : View, C1 : View, C2 : View, C3 : View } So if you want to access view builder tuple views, you need to do the same, like (tested with Xcode 12.4) struct ModelContentView: View { var body: some View { ModelView(content: { Text("Hello, world! 1") .padding() Text("Hello, world! 2") .padding() // Text("Hello, world! 3") // .padding() // Text("Hello, world! 4") // .padding() // Text("Hello, world! 5") // .padding() }) } } struct ModelView<Content: View>: View { var count: Int var content: () -> Content private init(count: Int = 1, #ViewBuilder content: #escaping () -> Content) { self.content = content self.count = count } var body: some View { content() } } extension ModelView { init<V1: View, V2: View>(#ViewBuilder content: #escaping () -> Content) where Content == TupleView<(V1, V2)> { self.init(count: 2, content: content) } init<V1: View, V2: View, V3: View>(#ViewBuilder content: #escaping () -> Content) where Content == TupleView<(V1, V2, V3)> { self.init(count: 3, content: content) } // ... all other variants }
First, by accessing Mirror(reflecting: content) you're reflecting the closure that creates Content (you can see that content is actually a closure of type () -> Content). You probably meant to reflect the view itself. For this you need to reflect the specific instance: let mirror = Mirror(reflecting: content()) // add the parentheses Then you tried to access the children. If you access it using the mirror above, you'll get (type formatted for clarity): AnyCollection<(label: Optional, value: Any)>( _box: Swift._RandomAccessCollectionBox< Swift.LazyMapSequence< Swift.Range<Swift.Int>, (label: Swift.Optional<Swift.String>, value: Any) > > ) Theoretically, you could then access the value of the first key (with the label value): if let value = Mirror(reflecting: content()).children.first?.value { print(value) } You'd get the result: ( SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._PaddingLayout>( content: SwiftUI.Text( storage: SwiftUI.Text.Storage.anyTextStorage( SwiftUI.unknown context at $7fff57642368.LocalizedTextStorage ), modifiers: [] ), modifier: SwiftUI._PaddingLayout( edges: SwiftUI.Edge.Set(rawValue: 15), insets: nil ) ), SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._PaddingLayout>(...) ) which is a tuple of type: ( SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._PaddingLayout>, SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._PaddingLayout>, SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._PaddingLayout>, SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._PaddingLayout>, SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._PaddingLayout> ) This is probably all you can get through reflection here. Important note From documentation: Mirrors are used by playgrounds and the debugger. You shouldn't probably use it in the real app.