iOS 14 has Changed (or broken?) SwiftUI GeometryReader - ios

As of today's release of iOS 14.0 my iOS code, which depends heavily on GeometryReader for layout, is no longer working well. I.e, layout has been kind of randomized.
In the Xcode debugger I have compared the same code running on iOS 13.6 vs iOS 14.0 and see that the problem is that the GeometryProxy structure is not being initialized in iOS 14.0.
In the example code below the variable g, a GeometryProxy, has valid width and height values in iOS 13 but is all zero's in iOS 14. (This is just one of the simpler uses I make of GeometryProxy values -- I would appreciate if nobody points out the obvious fact that this snippet could be accomplished much more easily in another way.)
Is this a change to the GeometryReader or a bug? Anyone have a workaround? Did I make a huge mistake depending on GeometryReader for dynamic layout?
struct TextView: View {
#EnvironmentObject var data: RecorderData
let m = MusicalNote()
var body: some View {
GeometryReader { g in
Stack(alignment: .leading) {
Text(self.data.curNote.getString())
.font(.system(size: g.size.height / 2.4))
.padding(.bottom)
Text(self.data.fdata.fingerings[self.data.curRecorder.id] ![self.data.curNote.seq] ![self.data.curFingType] ![self.data.currentFingeringChoice].comment)
.font(.headline)
}
Spacer()
}
}
}

It's definitely changed - this post sums it up: https://swiftui-lab.com/geometryreader-bug/
GeometryReader now lays out its content different to how it used to. It used to centre it horizontally and vertically in the parent but now it aligns it to the top-left.
My app looks awful - what's frustrating is it's not clear if this is new behaviour and is as-designed, or whether it's a bug and will be fixed. I'm trying to go through my app and manually apply offsets but the dynamic nature of layouts using GeometryReader means it is not always easy.

Editing my previous answer as its now obviously bad advice
From https://developer.apple.com/documentation/xcode-release-notes/xcode-12-release-notes
Rebuilding against the iOS 14, macOS 11, watchOS 7, and tvOS 14 SDKs changes uses of GeometryReader to reliably top-leading align the views inside the GeometryReader. This was the previous behavior, except when it wasn’t possible to detect a single static view inside the GeometryReader. (59722992) (FB7597816)
Which says, to me at least, that the previous behaviour was buggy by their standards - despite it matching the conventions for other layout containers like VStack. And that this behaviour is the new normal.

Related

Programmatically Collapse NavigationSplitView to One Column

I have developed a SwiftUI NavigationSplitView app with two columns and it works as expected.
I have chosen dynamic fonts and crafted the visual elements to respond to user settings
for visual accessibility. I know that a number of the users who will employ this app will have
the font size set to the largest available. While that enhances their view of the detail
screen, it pretty much makes the sidebar unreadable. I'm trying to find a way to change
the app's behavior based on the size of the dynamic type. Specifically, I'd like to have
the iPad behavior the same as an iPhone behavior based on a font size that I define.
As it is, the app on an iPhone collapses to a single column. I have not been able to
find a way to programmatically do this on an iPad.
I have tried:
Setting the horizontalSizeClass and as expected, this is a getter only.
.horizontalSizeClass = .compact
I have tried setting the column width of the detail to 0.
.navigationSplitViewColumnWidth(0)
that does not work
I have tried using the old stack function.
.navigationViewStyle(StackNavigationViewStyle())
that does not work.
Clearly, this can be done, since it is automatic when the device is an iPhone. I guess
I could create two views - one stacked and one split and choose based on a State variable
but that seems a bit crude.
Any guidance would be appreciated. Xcode 14 beta 6, iOS 16
This is a great idea and can be achieved by overriding the horizontalSizeClass in the environment depending on the environment value of dynamicTypeSize as follows:
struct NavigationSplitViewTest: View {
#Environment(\.dynamicTypeSize) var dynamicTypeSize
var body: some View {
ViewContainingSplitView()
.environment(\.horizontalSizeClass, dynamicTypeSize > .large ? .compact : .none)
}
}
I tested it by launching the app on iPad simulator and it was in 2 column, then switching to Settings to increase the type size by one notch then back to app and it had switched to single column. However when attempting to switch the size back I noticed the show/hide column button disappeared so I think we have some feedback to submit.
I still have not found an elegant way to do this, but my solution here does work.
Basically, I code for NavigationSplitView and for NavigationStack and change based on the
Environment(.dynamicTypeSize). Like this:
if dynamicTypeSize <= .xxLarge {
NavigationSplitView(columnVisibility: $columnVisibility) {
//all the code
} detail: {
//all the code
}
} else {
NavigationStack {
//all the code
}
}
For others, I was confused by the terms involved here. There is DynamicTypeSize, dynamic
fonts and Larger Accessibility Sizes. Dynamic fonts are those pre-built fonts that
support dynamic type out of the box - .title, .headline, .body etc. Dynamic type size is
the slider in settings that allows the user to scale the dynamic fonts to suit their
needs - all the dynamic fonts scale with the slider setting. Then on top of the slider
in settings for these pre-made fonts, is the option for Larger Accessibility sizes
which gives the user even bigger options.
My scheme above supports all of those intermingled options for both iPad and iPhone.

Apps complied with xCode 11.7 but running on iOS 14+ issues

So I made the "mistake" of upgrading one of my devices to iOS 14 (currently iOS 14.0.1), which I found out that now I can NO longer deploy to that device via xCode, since I am still developing with xCode 11.7 and not the newest version of xCode 12.
I am still able to deploy to the device with iOS 14, because of utilizing testflight, regardless of it being on the newest iOS, but I am getting some really weird results!!
This is what the screen SHOULD look like, which is running on a device with iOS 13.7:
versus the result when the device is on the newer iOS 14.0.1 version:
Has anyone else witnessed this!?
Couple of questions:
How does the text go from black to blue!?
The main image is all blue vs rendering the true image!?
In the case of the picker for "Origin" the image of the country flag is also all blue vs rendering correctly?
Is this the expected behavior of any app that is uploaded for sale in the iTunes Store which is compiled under an older version of xCode, my case xCode 11.7, vs the newest version?
I mean in the instance any person downloads the current version of the app created in an older version of xCode, but that user has the newest version of iOS on their phone, will the app become all messed up like that!? If so, what was apple thinking!?
Can anyone else provide ways they got around it, or what they experienced?
*** UPDATE ***
Providing the code for the text fields and pickers:
Here is the "default" Textfield:
TextField("Cigar Name", text: $model.cigarName)
Here is the code for the custom picker:
NavigationLink(destination: CountryPicker(cCode: $countryCode, cName: $countryName)) {
Text("Origin")
if countryCode != "" {
Spacer()
HStack() {
Image(uiImage: UIImage(named: "CountryPicker.bundle/Images/\(countryCode)")!)
.clipShape(Circle())
Text(countryName)
}
}
}
Code for "default" picker:
Picker(selection: $selectedCigarStrength, label: Text("Strenght")) {
ForEach(0 ..< cigarStrength.count) {
Text(self.cigarStrength[$0])
}
}
*** Latest Update ***
So I got my new MacBook and d/l xCode 12 for deployment to iphone with iOS 14.0.1 and this is what the screen shot looks like in comparison:
As you can see it looks way different!!! Good news is that the blue images went back to rendering the correct images in the sheet.. bad news is that the text is still blue!? I guess developers will have to strictly specify the text color going forward!? Which is weird... if no color is declared I would imagine it should stay .black by default... as it did in all other versions of xCode!?
Notice too that the form does not extend to the edge of the screen anymore and the date picker has really changed (which I actually like, finally something they actually improved in the new OS).
Me2 but I do not have a solution. I switch back from xcode 12 to xcode 11.7
Based on the issue perhaps it should be the defaults textColor property changed by the system, if possible, please show the code about the textColor for UILabel.
It would seem that iOS 14, for me 14.0.1, has serious issues with colors!?
As noted above, now it would seem that you need to be explicit with the foregroundColor for Textfields and text alike and "force" them to a default color of black, as that used to be default... not sure why they changed that!
The same thing goes for Navigation titles (navigationBarTitle), if you move from one tab that utilizes the title to another tab which does not require it, it will not remove the navigation bar title in the new view for some reason, UNLESS you "trick" the system with navigationBarTitle("") other wise force the blank title.
Not sure why apple decided that everything needs to be explicit vs implicit as it was in the previous xcode and ios versions!?
Another, which I think is a biggest issue, I have uncovered is the evaluation of the Nil-Coalescing Operator.
I have the following condition:
Button(action: {
if (self.viewRouter.currentView != "menu") {
self.viewRouter.currentView = "menu"
}
}) {
Image("menu")
.renderingMode(.template) //**** REQUIRED!!!!
}
.foregroundColor(self.viewRouter.currentView == "menu" ? .blue : .gray)
This simple condition states that if the view currently clicked on is the "menu" then the color of the button should be blue vs if its not and a different view is being displayed it should be gray... the end result is that the button is black and no matter which I select in the tab bar they all remain black and not not change color as they do on my device that i am running iOS 13.6!?
*** UPDATE ***
Again ios 14 became a lot more explicit!! you need to add the .renderingMode(.template) in order to achieve the same result in the pre-ios 14 era.

SwiftUI TabView not responding

:-) Here comes a probably silly problem with a bit of a preamble sorry!
Long story short - I've been developing an app pulling my hair out due to a bug with the TabView in SwiftUI. I was overlaying the tabs with a ZLayer to add a funky big button in the middle - so when I was running into problems I thought this would be the issue... but this becomes irrelevant in a moment.
For simplicity I started a New project in XCode v11.6, build target iOS 13.6, Selected Tabbed App, created a very simple boilerplate SwiftUI content view with 2 tabs. (For clarity - I did not modify the default code at all). Runs perfectly on the simulator. Plug in my iPhone XS running iOS 13.6.1 (when I first encountered this problem I was on 13.5 so have updated iOS builds whilst this problem has occurred). Run the simple, non modified app on my phone. Loads up "First View", I tap the "Second" tab, nothing. No response at all. This is the same problem I am having in my more complicated app. Occasionally if I keep rebuilding. It will let me switch to "Second View" but then cannot switch back. The Tab Controller just seems to become non-responsive.
Now back to my more complicated app, on the first view there is a scroll view and there is a button with an attached actionSheet. If I pop open the action sheet, then dismiss it (regardless of just calling the .cancel() button or selecting an action), the Tab Controller works perfectly. There is another option which opens another sheet, again as soon as something has been overlayed, everything works as expected until the next app launch.
Tried resetting my MacBook, tried resetting my iPhone, tried uninstalling the build then rebuilding, cleaning the build folder, tried creating multiple projects with build targets iOS 13, 13.2, 13.5, 13.6. Problem seems to persist, but every time the simulator works perfectly.
So my questions here are: From my searching I can't find anyone with this problem. Is it just me really? Can anyone with XCode 11.6 and an iPhone XS please simply hit "New Project" -> "Tabbed App" and let me know if this works? (I'm almost wondering if there's a bug in XCode 11.6 when you create an app and everyone is either working on projects that were already created pre XCode 11.6 or already using the beta XCode... But really I am just out of ideas)
Also is there any deeper debugging one can do? Something like actually notifying me for every touch it receives and whether the App sandbox is getting the touch or the OS is (for some reason) keeping the tap gesture? I've never experienced something like this so I can't really see where to start debugging as I don't even know if the app is receiving the tap.
Or frankly - does anyone have any other ideas?
Just for clarity - ContentView looks like this: (Again it's straight from what XCode creates for a Tabbed App)
import SwiftUI
struct ContentView: View {
#State private var selection = 0
var body: some View {
TabView(selection: $selection){
Text("First View")
.font(.title)
.tabItem {
VStack {
Image("first")
Text("First")
}
}
.tag(0)
Text("Second View")
.font(.title)
.tabItem {
VStack {
Image("second")
Text("Second")
}
}
.tag(1)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Thanks in advance and sorry for the long ramble - from a very sleepy dev at wits' end!

Swift: Has anyone had code work properly on their device, but not in simulator?

I was building an application at the beginning of this year up until around March, and then I took a break and recently wanted to pick it up again. However, my code was behaving differently from when I last used it. I had not made any changes to the code, and strangely even past versions from months prior also weren't working, and those certainly weren't changed.
From some troubleshooting here are things that worked based on this code:
Here is the main ContentView class
var body: some View {
return ZStack {
setColor
.edgesIgnoringSafeArea(.all)
.statusBar(hidden: true)
VStack(alignment:.trailing){
VStack{
DisplayBar(majorKeyManager:majorKeyManager)
}.zIndex(2.0)
VStack{
GuitarView(guitarModel:guitarModel)//.position(x:556, y:220)
}.padding(.bottom, 20)
VStack{
PianoView(pianoModel: pianoModel)//majorKeyManager: majorKeyManager, audioEngine: audioEngine)//.position(x:556,y:600)
}
}
}.edgesIgnoringSafeArea(.top)//.padding(10)
}
Then here is the GuitarView
struct GuitarView: View {
#ObservedObject var guitarModel:GuitarModel
var body: some View {
var needsMarker = false
var needsIndexMarker = false
var needsCapo = false
return HStack (spacing:0){
VStack{
ForEach(0..<guitarModel.numStrings){ stringIndex in
VStack{
Here is the PianoView
struct PianoView: View {
#ObservedObject var pianoModel:PianoModel
var body: some View {
return VStack(alignment:.leading){
ZStack{
HStack (spacing:2){ // DRAW WHITE KEYS
ForEach(0..<self.pianoModel.whiteKeyMap.count){ (keyIndex) -> KeyButtonView in
I didn't think it was important to show the details of what is happening within GuitarView/PianoView. They are structured very similarly, except the difference is the PianoView always behaves properly, while the GuitarView is hit or miss. I placed a red line in the GuitarView image that shows where it is not entering the ForEach, yet it is meeting the conditions of the ForEach (the variable it is checking is always 12).
Both views are being updated based on their respective models which are ObservedObjects. The model is correctly being updated for both the Guitar and Piano. However, GuitarView gets called once when the initial program is launched, but is sometimes never called again. However, this varies depending on whether I'm running it within device simulator, versus a real device and which XCode version I am using.
On XCode 11.4.1, the code does not work properly on the device simulator on any device (Mac, iPhone, iPad, etc.). However, it does run properly on real devices like an iPhone and an iPad. I tried reinstalling XCode but that changed nothing.
Then, I tried rolling back my version of XCode to 11.2.1, and the code runs properly in the iPad simulator, but not as a Mac simulator.
Has anyone had any similar problems to this? I can't seem to figure out why this code gets called once, but then never gets called again despite its ObservedObject being clearly updated, and another nearly identical view (PianoView), is clearly working.

How to use the .monospaced system font design

I am attempting to use a system font and apply the monospaced design, without luck. I can successfully make the text monospaced using the custom font function and passing in Courier and a size, but this is not idea because then the font size is fixed.
VStack {
Text("lmlmlmlm 12345678")
Text("lmlmlmlm 12345678")
.font(Font.system(.body, design: .monospaced))
Text("lmlmlmlm 12345678")
.font(Font.custom("Courier", size: 18))
}
How do I get the system font to work with the .monospaced design? I think it might be a bug with .monospaced, because the .serif option does modify the text as expected.
It seems .monospaced font only applies when given a fixed size:
Text("monospaced")
.font(.system(size: 14, design: .monospaced))
This won't work given a dynamic text style such as body. But as you've also mentioned it works fine for other fonts so this is probably a bug in Xcode 11.0 beta and hopefully will be fixed in next releases.
Update:
This issue was fixed with Xcode 11 beta 3. The following code works now:
Text("monospaced")
.font(.system(.body, design: .monospaced))
In case you want to make only digits monospaced, you might try using something like this:
Text("0123456789")
.font(Font.system(.body, design: .monospaced).monospacedDigit())
This does not circumvent the obvious bug of Xcode 11.0 beta, however. Letters are still not rendered monospaced.

Resources