Posts

Post marked as solved
7 Replies
3.1k Views
I have a custom modifier which is only used on a Text view. I want to include an underline call in it, but get an error. My understanding is that this is because underline is unique to Text views whereas the ViewModifier protocol isn't.Is there a way to specify this?struct activeEquationString: ViewModifier { let termInd: Int @Binding var currentInd: Int func body(content: Content) -> some View { content .underline(currentInd == termInd) // <-- Value of type 'activeEquationString.Content' (aka '_ViewModifier_Content<activeEquationString>') has no member 'underline' .foregroundColor(currentInd == termInd ? Color.black : Color.gray) } }
Posted Last updated
.
Post marked as solved
7 Replies
1.3k Views
(This post follows on from the solution to Untraceable SIGABRT: precondition failure: attribute failed to set an initial value: <num>)Hopefully one of the last posts I make as I sprint toward this project's end. The issue involves a lot of code that wouldn't be practical to paste here, so I'll do my best to simplify some and leave other bits out completely. If you need the full thing I could maybe email the relevant files but for now hopefully this will do.I have a Dots(_:) view which takes a single parameter, quantity. There is then a chunk of complicated looking code that ultimately randomly places them on screen - this bit has been left out of the post.struct Dots: View { @State private var positions: [DotPosition] = [] let quantity: Int let dot: some View = Circle() .size(width: diam, height: diam) .fill(Color.red) init(_ quantity: Int) { self.quantity = quantity self._positions = State(initialValue: createPositions()) for pos in positions { print(pos)} // <- this is showing that new values ARE being made } var body: some View { GeometryReader { geom in ForEach(self.positions, id: \.self) {position in self.dot .offset(position.offset(maxWidth: geom.size.width, maxHeight: geom.size.height)) // ^ a print statement in here shows iteration over the new values, and then the very first values } } } }The file that could effectively be used as ContentView/root for this problem, again simplified, isstruct SwipableEquation: View { let num: Int var body: some View { VStack { GeometryReader { geo in HStack(spacing: 0) { Group { Dots(self.num) // <- prints show a new instance gets created when exepcted, but the view doesn't change } } } Text(String(num)) // <- this value actively changes as and when expected } } } struct ContentView: View { var nums: [Int] = [2,3,4] @State private var currentEqn = 0 var body: some View { VStack { SwipableEquation(num: nums[currentEqn]) HStack { // Previous Eqn Button(action: { self.currentEqn -= 1 print("\n######### BACK #########") }) { Image(systemName: "chevron.left") VStack { Text("Previous Equation") if currentEqn > 0 { Text(String(nums[currentEqn-1])) } } }.disabled(self.currentEqn == 0) Spacer() // Next eqn Button(action: { self.currentEqn += 1 print("\n######### NEXT #########") }) { VStack { Text("Next Equation") if currentEqn < nums.count-1 { Text(String(nums[currentEqn+1])) } } Image(systemName: "chevron.right") }.disabled(self.currentEqn == nums.count-1) }.padding() } } }
Posted Last updated
.
Post marked as solved
3 Replies
3.4k Views
Note: If the Xcode canvas doesn't show anything in Live Preview, try an actual Simulator - no idea why canvas wasn't working in this demo but not important to the main issue.I have a view with several pickers. Rather than flooding the namespace, I used an array storing the values. This works fine.To improve it, I decided it would be more maintainable to use a dictionary, however, after making the switch, although there are no compile or runtime errors, it appears the picker's selection parameter isn't able to read or write to the dictionary?struct ContentView: View { @State private var data = [ // <- doesn't appear to be used by picker 1 "key1": 0, "key2": 5 ] @State private var indices = [Int](repeating: 0, count: 2) // <- updates fine var body: some View { NavigationView { Form { // Picker 1, doesn't work Picker(selection: $data["key1"], label: Text("dict")) { ForEach(0..<100) { Text(String($0)) // <- shows no value } } // Picker 2, works fine Picker(selection: $indices[0], label: Text("array")) { ForEach(0..<100) { Text(String($0)) } } } } } }
Posted Last updated
.
Post marked as solved
12 Replies
3.2k Views
Edit: Small changes to posted code to allow easier testing in tmp project. Create a new Single View App that using SwiftUI. C&P View A into ContentView.swift, and View B into a new files called "Dots"I have also found that the issue does not occur when passing 0 to Dots, and have added that to the picker to aid testing.I have spent countless hours doing my best to solve this. Read through too many other posts about this issue being caused by a lot of different things, hacked and slashed code and simple print statement/lldb to try track it down and I'm getting now - I'm pretty frantic right now.This is the same core problem as a previous postView works fine until used in NavigationView/LinkWhilst the workaround stopped the crashes, it was definitely a workaround and not fixing the source. As a consequence, a different "bug" appeared why a custom view partially ignored safe boundaries*. Rather than trying to find a hacky workaround for the workaround, it would be far better to try solve the core issue, but am deeply struggling.As explained in the previous post, View A and B work fine on their own, but when navigation from A to B, that when it fails. Stepping through the code didn't yield much but it be a case of not knowing what to look for.* this view consisted of x views that behaved like horizontal cards, swiping left/right to jump between them. This was achieved by having a HStack(spacing: 0) with a frame x times the width of the screen and a custom Gesture. If you envisage the views as an array, index 0 would be shown by default, and one can swipe amongst them. The first 2 items would correctly respect the inherited height, but 3 onwards would go behind the navigation space and status bar.Note I've attempted to clear the code of some unnecessary content so as to avoid wasting your time.View A (Note I've attempted to clear the code of some unnecessary content so as to avoid wasting your time.)import SwiftUI let lowerBound = 0 let upperBound = 101 struct CapsuleButton: ViewModifier { func body(content: Content) -> some View { content .frame(width: 250, height: 100) .background(Capsule().fill(Color.blue)) .foregroundColor(.black) .font(.system(size: 28, weight: .light)) .padding() } } struct ContentView: View { @State private var pickerIndex: Int = UserDefaults.standard.integer(forKey: "highestDotCovered") - lowerBound @State private var nav: Int? = 0 var body: some View { VStack(spacing: 30) { HStack { Picker(selection: $pickerIndex, label: Text("")) { ForEach(lowerBound..<upperBound) { Text(String($0)) } } } Spacer() NavigationLink(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 } } .navigationBarTitle("Quantity Selection") } } struct ContentView_Previews: PreviewProvider { static var previews: some View { NavigationView { ContentView() }.navigationViewStyle(StackNavigationViewStyle()) } }View Bimport SwiftUI private let diam: CGFloat = 60 // ~1/3", 1pt = 1/163" private let maxPhoneDots = 12 struct Dots: View { @State private var dotAlert = false let quantity: Int let dot: some View = Circle() .size(width: diam, height: diam) .fill(Color.red) @State var offsets: [CGSize] = [] init(_ quantity: Int) { let model = UIDevice().model self.quantity = model == "iPad" ? quantity : min(quantity, maxPhoneDots) print(self.quantity) } var body: some View { // NavigationView { // need to wrap geom reader in nav view to avoid crash // VStack { GeometryReader { geom in ForEach(self.genOffsets(maxWidth: geom.size.width, maxHeight: geom.size.height), id: \.self) {offset in self.dot .offset(offset) } .background(Color.clear .contentShape(Rectangle())) // workaround for allowing gestures on clear background } .border(Color.black) // Spacer() // Text("Dots: \(self.quantity)") // } // .navigationBarTitle("") // .navigationBarHidden(true) // }.navigationViewStyle(StackNavigationViewStyle()) } } extension Dots { func genOffsets(maxWidth: CGFloat, maxHeight: CGFloat) -> [CGSize] { var cols = UIDevice().model == "iPad" ? 8 : 4 var rows = UIDevice().model == "iPad" ? 13 : 5 if UIDevice().orientation.isLandscape { (cols, rows) = (rows, cols) //swap cols and rows } var offsets: [CGSize] = [] let 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)) let xJitter = colSeparation * 0.49 let yJitter = rowSeparation * 0.49 // grid[row][col] var grid = Array(repeating: Array(repeating: false, count: cols), count: rows) // Generate dot offsets for _ in 0..<quantity { var r, c: Int // Find a random empty cell repeat { r = Int.random(in: 0..<rows) c = Int.random(in: 0..<cols) } while grid[r][c] grid[r][c] = true // Calculate dot's offset // Initial padding from TL corner var x = colSeparation var y = rowSeparation // Separate dot into standard grid slot x += CGFloat(c)*(diam+colSeparation) y += CGFloat(r)*(diam+rowSeparation) // Apply jitter if xJitter != 0 { x += CGFloat.random(in: -xJitter ..< xJitter) } if yJitter != 0 { y += CGFloat.random(in: -yJitter ..< yJitter) } 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) } } struct Dots_Previews: PreviewProvider { static var previews: some View { Dots(1) } }
Posted Last updated
.
Post not yet marked as solved
1 Replies
936 Views
Afaik I can't imbed images on here, so this may be difficult but let's try.On iPad, my App is limited to Landscape. The home screen is the default navigaton view i.e. master/detail split. Within the detail split, the user will edit a few configurations and then tap on a "Continue" button which is actually another NavigationLink. I want the destination of this link to become full screen as if .navigationViewStyle of the top NavigationView switched from DefaultNavigationView() to StackNavigationView().Is this possible?
Posted Last updated
.
Post marked as solved
1 Replies
1k Views
My ContentView has a NavigationView at the top level, then some arbitrary views and then a NavigationLink. I can go into more detail on the code and other views here if someone asks, but I'm not sure it's important right now.I also have a view called Dots. The somewhat simplified code is:private let diam: CGFloat = 60 // ~1/3", 1pt = 1/163" private let maxPhoneDots = 12 struct Dots: View { let quantity: Int let dot: some View = Circle() .size(width: diam, height: diam) .fill(Color.red) var offsets: [CGSize] = [] init(_ quantity: Int) { let model = UIDevice().model self.quantity = model == "iPad" ? quantity : min(quantity, maxPhoneDots) } var body: some View { VStack { GeometryReader { geom in // Call to placement algorithm ForEach(self.genOffsets(maxWidth: geom.size.width, maxHeight: geom.size.height), id: \.self) {offset in self.dot .offset(offset) } .background(Color.clear .contentShape(Rectangle())) // workaround for allowing gestures on clear background //Some unimportant views // ... } } } extension Dots { // function using for clarity of calculation done without flooding main function with unnecessary variables/constants func calcSeparation(maxLength: CGFloat, dotsNum: Int) -> CGFloat { return calcSeparation(maxLength: maxLength, dotsNum: CGFloat(dotsNum)) } func calcSeparation(maxLength: CGFloat, dotsNum: CGFloat) -> CGFloat { efforts //the max(value, 0) was part of debugging efforts let availableLength = max(maxLength - (CGFloat(dotsNum) * diam), 0) if availableLength <= 0 { print("WARNING: avail=\(maxLength)")} // need +1 separation for inital gap return availableLength / CGFloat((dotsNum+1)) } func genOffsets(maxWidth: CGFloat, maxHeight: CGFloat) -> [CGSize] { // TODO More dynamic limits as f(dims, diam) let cols = UIDevice().model == "iPad" ? 8 : 4 let rows = UIDevice().model == "iPad" ? 13 : 5 var offsets: [CGSize] = [] // LOOK HERE let colSeparation: CGFloat = calcSeparation(maxLength: maxWidth, dotsNum: cols) // rowSep, commented // grid[row][col] var grid = Array(repeating: Array(repeating: false, count: cols), count: rows) // Generate dot offsets for _ in 0..<quantity { var r, c: Int // Find a random free cell repeat { r = Int.random(in: 0..<rows) c = Int.random(in: 0..<cols) } while grid[r][c] grid[r][c] = true // Calculate dot's offset // Initial padding from TL corner var x = colSeparation // ... code for height offest (y) and additional adjustments, currently commented out offsets.append(CGSize(width: x, height: 0)) } return offsets } } extension CGSize: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(width) hasher.combine(height) } }Line 53 appears to be the cause of a crash due to :let colSeparation: CGFloat = calcSeparation(maxLength: maxWidth, dotsNum: cols)The crash also happened when it was a computed property.Initialising it first (and changing to var) prevents the crash,var colSeparation: CGFloat = 0 colSeparation = calcSeparation(maxLength: maxWidth, dotsNum: cols)or alternatively taking the code out of function like so:let availableLength = max(maxWidth - (CGFloat(cols) * diam), 0) let colSeparation: CGFloat = availableLength / CGFloat((cols+1))However, instead of crashing, whilst the individual Dots (pre)view still works as expected, navigating to it (via NavigationView<NavigationLink>) yields an empty screen. Not even the automatically configured "back" navigation appears.It is something to do with the GeometryReader values as it initialises and updates?Note this is my first Swift/SwiftUI project.
Posted Last updated
.
Post not yet marked as solved
0 Replies
478 Views
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 }
Posted Last updated
.
Post not yet marked as solved
0 Replies
932 Views
Original Post and title "View property depends on GeometryReader sizes"So, I have a view that is a collection of randomly placed dots. Currently the computation of these dots is just random within the size that GeometryReader gives, but it become more complex later (it will need to be iterated over and referred to for adjustments and thus needs to be stored somewhere). For a given instance of Dots, after calculation they will remain constant (in usage, there may be multiple instances on screen, it may also be gestured off screen but then gestured back to, hence being stored).The problem is I need the values from GeometryReader, but you can't declare variables in a ViewBuilder and self is immutable.There's also the issue in using ForEach that CGSize doesn't conform to Hashable, but one problem at a time...func randomOffset(maxWidth: CGFloat, maxHeight: CGFloat) -> CGSize { // ... } struct Dots: View { let quantity: Int let dot: some View = Circle() var offsets: [CGSize] = [] init(_ quantity: Int) { self.quantity = quantity } mutating func genOffsets(maxWidth: CGFloat, maxHeight: CGFloat) { for _ in 0.. // will be more complex later... self.offsets.append(randomOffset(maxWidth: maxWidth, maxHeight: maxHeight)) } } body: some View { GeometryReader { geom in genOffsets(maxWidth: geom.size.width, maxHeight: geom.size.height) ForEach(offsets, id: \.self) {offset in self.dot .offset(offset)) } } } }Edit: In terms of getting a variable inside of GeometryReader (or other ViewBuilders), it looks like the issue it to do with implicitly specifying return type https://forums.swift.org/t/local-vars-in-swiftui-builders/26672/7Adding a return is still valid, but once the variable is there it errors saying the generic type Content can't be inferred, thus the de-generalising needs to be stated explicitly. For example, this worksGeometryReader<Text> { _ in let s = "hello" return Text(s) }thanks to the de-generlising GeometryReader's '<Content: View>' to '<Text>' and an explicit 'return' call.But the view in my case is rather more complex than a simple 'Text' view and would be <VStack: <HStack: ...>> and so on. Is there something in between I can do?
Posted Last updated
.
Post marked as solved
6 Replies
2.3k Views
I'm in the process of making my first app (ever) using SwiftUII've made a fairly simple custom view for my main navigation linksstruct MainMenuCapsule: View { let label: String private let dest: NavDestination init(_ label:String, destination: NavDestination) { self.label = label self.dest = destination } var body: some View { NavigationLink(label, destination: dest) .frame(width: 250, height: 100) .background(Capsule().fill(Color.newPrimaryColor)) .foregroundColor(.black) .font(.system(size: 28, weight: .light)) .padding() } }For my home screen, I want to use something clean like a ForEach iterating over a list of (label: String, dest: View) pairs to generate the list of capsule-links. (I briefly tried using List but the automatic NavLink formating didn't fit my design and I struggled to edit it - comments on using that approach are also welcomed.)I've been googling and playing with things for a few hours it seems, and it doesn't seem possible (with the original issue being "Heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional" - if I do that, it allows the array's declaration but issue ensue with trying to use it) but I wanted to check here before resorting to an explicit call for each MainMenuCapsule instancestruct ContentView: View { let menuItems = ["Recommended", "Browse", "Guidelines"] // The style of array I want to use // let menuItems = [("Recommended", Dots(2)), // ("Browse", Placeholder()), // ("Guidelines", Placeholder())] var body: some View { VStack { // Nav Icons HStack { Image(systemName: "info.circle.fill") Spacer() Image(systemName: "gear") } .padding(.horizontal) NavigationView { VStack(spacing: 30) { // Title and welcome message HStack { VStack(alignment: .leading) { Text("Welcome") .font(.largeTitle) Text("Let's learn some maths!") .font(.system(size: 20, weight: .light)) } .padding(.horizontal, 30) Spacer() } // Main menu ForEach(0..<menuitems.count) {i="" in<br=""> MainMenuCapsule(self.menuItems[i], destination: Placeholder()) } Spacer() } } } } }PS Any comments on bad swift code style are also welcomed, I take pride in trying to follow standards and doing things in the "best" way, but I've got a lot to learn;)
Posted Last updated
.
Post marked as solved
3 Replies
2.1k Views
Hey - First off, apologies if I'm failing to meet any standard or conventions with this post - never used forums much and I'm certainly new to apple dev ones!So, I'm a final year CompSci student and my thesis involves putting together a fairly simple app. It's my first time developing an app, (first time on a Mac as well but not too worried about that, it'll simple take a bit of getting used to and/or sub optimal use, but that's not why I'm here) and I've been looking at a few guides and tutorials, namely https://www.youtube.com/watch?v=09TeUXjzpKs and https://www.appcoda.com/learnswift/build-your-first-app.html.I tried Apple's own "Getting started" guide (https://developer.apple.com/library/archive/referencelibrary/GettingStarted/DevelopiOSAppsSwift/BuildABasicUI.html#//apple_ref/doc/uid/TP40015214-CH5-SW1 ) but it seems to be even more out of date than the others.Ultimately, the files aren't as expected - I'm sure this is just a change from version updates, though the appcoda resource is using both xCode 11 and iOS13 (not certain on which swift it is).Project File changes:- ViewController.swift- MainStoryboard (This is the biggie, it seems to be the first port of call in said guides)+ SceneDelegate.swift (I believe this came from iOS 13 and contains some bits from the old AppDelegate)+ ContentView.swift (Is this just a rename of ViewController?)Could someone recommend an up-to-date tutorial/getting started resource or possibly give an overview of the changes I need to keep in mind if using these slightly older ones?
Posted Last updated
.