Yep, the big thing I didn't know when making this post was that SwiftUI was a (relatively new) thing and an alternative to storyboard. I've since decided to get stuck into SwiftUI having found some good resources (including Apple's) as it seems a good investment at the risk of less help and documentation since it's new.
Post
Replies
Boosts
Views
Activity
Hey, thanks, encapsulating each View inside the array in an "AnyView()" seems to have worked without any side effects!The "NavDestination" was a generic I made to allow View passthrough to MainMenuCapsule - when I copied the code onto here it looks like some items slipped through a bit skewy// original post
struct MainMenuCapsule: View {
// actual code
struct MainMenuCapsule<NavDestination: View>: View {
Yeah I figured it was from the angled brackets.A development related to this post - I had a similar situation but where I actually wanted the List style over ForEach. However this has issues as the Array of pairs doesn't conform to Hashable.As a solution to this and to generally improve code structure I decided to make a stuct to represent the pair. After some playing and research I got the following to work successfully.struct NavLinkPair<NavDestination: View> {
let label: String
let dest: NavDestination
init(_ label:String, _ destination: NavDestination) { // dest param won't use _ once I get it working btw
self.label = label
self.dest = destination
}
}
struct MainMenuCapsule<Content: View>: View {
private let navPair: NavLinkPair<Content>
init(_ navPair: NavLinkPair<Content>) {
self.navPair = navPair
}
// body ...However, when replacing the old "let menuItems: [(label: String, dest: AnyView)] = ..." with the use of the new struct it produced to original heterogeneous errors. As you said in your first reply "As you found, you cannot create an Array of different Views practical enough to use it."Am I just wasting my time pursuing things like this?Bothlet test = [NavLinkPair("Label", Text(""))]
let test2 = [NavLinkPair("Label", Image(somePath))]work fine, but as you'd expect, trying to combine them fails.
Great answer and explanation, top marks!;) I didn’t really understand the issue originally as I saw ‘Text()’ and ‘Image()’ (and practically everything else in SwiftUI) as of the type ‘View’ since that’s what they return but I guess it’s a little more complicated than that. Come morning I’ll try your suggestion of replacing the generic type with AnyView since it looks more accurate to what I’m trying to do? Honestly never used generics before this, but saw it as a solution to a similar issue/post involving View pass through.
It seems very similar or at least related to this issue https://forums.developer.apple.com/thread/129171#406496I wrapped the top level in a NavigationView as per the other thread as well as removing the functions declared on line 35 and 39, consequently replacing 54 (and 55) withlet availableWidth = max(maxWidth - (CGFloat(cols) * diam), 0)
let colSeparation: CGFloat = availableWidth / CGFloat((cols+1))
let availableHeight = max(maxHeight - (CGFloat(rows) * diam), 0)
let rowSeparation: CGFloat = availableHeight / CGFloat((rows+1))Finally, to deal with the automatic implications of a NavigationView, I added the modifiers.navigationBarTitle("")
.navigationBarHidden(true)to the VStack (ie first view under Nav)and if on iPad you'll likely want.navigationViewStyle(StackNavigationViewStyle())applied to the actual NavigationView.Since the "fix" required an additional and otherwise irrelevant NavigationView, I'm led to believe this is an actual bug in the SwiftUI framework, but can't be certain.
Edit: It appears my issue was just a matter of placing the $ binding in the right placeForEach(0..<ops.count) { i in
Toggle("Toggle", isOn: self.$ops[i].isSelected)
.toggleStyle(OperatorToggle(op: self.ops[i]))
}I have a similar setup with a few differences.using ForEach instead of Listinstead of an array of a struct, I have an array of structs conforming to a common protocolthe ForEach is iterating over a 0..<myArray.countSo far I've just tried to use your code but with my variable but my efforts so far have failed.struct OpSelect: View {
@State private var ops: [Operator] = [Add(), Subtract(), Multiply(), Divide()]
var body: some View {
List($ops, id: \.self) { (op : Binding) in
//^ Cannot convert value of type '(Binding) -> _' to expected argument type '(_) -> _'
Toggle(isOn: op.isSelected) {
//^Cannot convert value of type 'Toggle' to closure result type '_'
Text("My Toggle")
}
}
}The code detailing the Operatorsprotocol Operator {
var name: String { get }
var symbol: String { get }
var isSelected: Bool { get set }
// var inverse: Operator { get }
func operate(on a: Int, and b: Int) -> Int
// func inverse(on a: Int, and b: Int) -> Int
}
extension Operator where Self: Hashable {} // https://stackoverflow.com/questions/50966560/make-a-protocol-conform-to-another-protocol
struct Add: Operator {
let name = "add"
let symbol = "plus"
var isSelected = false
// var inverse: Operator = Subtract()
func operate(on a: Int, and b: Int) -> Int {
return a + b
}
}
struct Subtract: Operator {
let name = "subtract"
let symbol = "minus"
var isSelected = false
// var inverse: Operator = Add()
func operate(on a: Int, and b: Int) -> Int {
return a - b
}
}
struct Multiply: Operator {
let name = "multiply"
let symbol = "multiply"
var isSelected = false
// var inverse: Operator = Divide()
func operate(on a: Int, and b: Int) -> Int {
return a * b
}
}
struct Divide: Operator {
let name = "divide"
let symbol = "divide"
var isSelected = false
// var inverse: Operator = Multiply()
func operate(on a: Int, and b: Int) -> Int {
return a / b
}
}
Run Debug Preview on View A. When clicking "Continue", it should show View B. Instead, the terminal shows precondition failure: attribute failed to set an initial value: 578 (the number varies). Then in the editor, it auto navigates to the boiler plate AppDelegate to show the error:class AppDelegate: UIResponder, UIApplicationDelegate { // <- Thread 1: signal SIGABRTFor this purpose View A can be ContentView, so I'll quickly edit the post to allow you to run in a tmp xCode project.
With the code as posted, testing in Preview. Live preview, continue goes to a blank screen, in Debug preview, the described error occurs.I just ran a simulator and I get the behaviour of Continue now behaving as you described as well.
It activates the associated NavigationLink.It's like havingNavigationLink(destination: Dots(lowerBound + pickerIndex)) {
Text("Continue").modifier(CapsuleButton())
}but allows some extra code to be executed in between the user tapping the link, and the actually navigating to the destination i.e. Dots(num). In my case, it updates the stored UserDefaults. I believe the inspiration came from the solution for https://stackoverflow.com/questions/57130866/how-to-show-navigationlink-as-a-button-in-swiftuiThe issue still occurs if you have the above instead ofNavigationLink(destination: Dots(lowerBound + pickerIndex), tag: 1, selection: $nav) { EmptyView()
Text("Continue")
.modifier(CapsuleButton()) // custom look
.onTapGesture {
// Update storage
let currentHighest = UserDefaults.standard.integer(forKey: "highestDotCovered")
let highest = max(lowerBound + self.pickerIndex, currentHighest)
UserDefaults.standard.set(highest, forKey: "highestDotCovered")
// Go to destination
self.nav = 1
}
Indeed, user defaults was never the problem?That line triggers the associated NavigationLink, ie telling the NavigationLink instance that has the "nav" variable as the value for its "selection" property/parameter. You don't need to fixate on it, as I showed in the last post, the crash is unaffected if you remove it and use a basic NavigationLink that have something instead of the EmptyView().When you said earlier that replacing EmptyView() with Dots() works - this is not really related. What is important is that Dots() is the value of the NavigationLink's "destination", and when trying to navigate to that destination, with a dot quantity value other than 0, it crashes. Furthermore, simply including the same Dots() view on the page (like when you replaced EmptyView() with it) does not trigger the crash. Thus it is strictly related to navigating to the Dots view.Does that help clarify the issue at all?
Amazing, thank you!Some great ideas in code reorganisation here as well, namely separating the random aspects of a dot's position from the bit requiring the GeomertryReader values, which neatly eliminates the forced conformance of CGSize to Hashable. My initial approach had me thinking quite linearly.May I ask what difference betweenself._positions = State(initialValue: createPositions())and justself.positions = createPositions()is?Thanks again.
An interesting solution.On the linefunc modifier(_ theModifier: TM) -> some View { is TM supposed to be TextModifier?If so, that causes the error "Protocol 'TextModifier' can only be used as a generic constraint because it has Self or associated type requirements"
Ah yes, an issue I am well acquainted with. I'd recommend updating your original post (which I've marked as the soln) in case others come here.Personally, I went in a slightly different design direction using a rounded rectangle outline, but thank you all the same.
Interestingly that post didn't come up with me googling efforts, maybe since it doesn't have any activity on it? Nevertheless, thanks for the reply.
This is probably what you meant by "do it manually", but I've personally just been using screen snippets (Cmd+Shift+4). Slightly better than Screenshot -> Crop at least.