Programmatically Collapse NavigationSplitView to One Column - ios

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.

Related

iOS Swift: maximumContentSizeCategory not working on UILabel

According to this article:
https://useyourloaf.com/blog/restricting-dynamic-type-sizes/
And this WWDC 2021 video:
https://developer.apple.com/videos/play/wwdc2021/10059/?time=879
The minimumContentSizeCategory and maximumContentSizeCategory can limit the min and max font size when user uplevel/downlevel the font sizes in System Accessibility settings.
However I tried it out, and from the debugging info these two settings are properly set, but in view rendering, the font size still goes to very large if accessibility font size is very large.
// before setting
print(label.appliedContentSizeCategoryLimitsDescription)
label.minimumContentSizeCategory = .small
label.maximumContentSizeCategory = .accessibilityMedium
// after setting
print(label.appliedContentSizeCategoryLimitsDescription)
print results:
UILabel:0x7fd610a414a0: AX-L
--> AX-L
UILabel:0x7fd610a414a0: S <= (none->)AX-M <= AX-M
--> AX-M
How can I properly use these two settings?
Any ideas on how to properly use these two settings?
I hope that's the proper way because I use them as you did (and it works).
I created a blank project in Interface Builder (Xcode 13.4.1) as follows:
... and the Dynamic Type feature is blocked within the specified thresholds I defined like you (in the viewDidLoad of the view controller).
I think you should check out the way you created your label because, with the above one, it works like a charm.

swift ios macCatalyst app does not load some views on startup on macOS Monterey

since the release of the new macOS Moterey operating system (12) my MacCatalyst application does not load some views at startup until I resize the window or change focus.
If I look at XCode's Debug View Hierarchy these views are not present.
If, on the other hand, I use the classic debug (while they are not shown) it turns out that they have the correct frame (origin and size), superviews and window.
To show them I have to resize the window (manually, using the mouse) or remove the focus from the app (for example by clicking on another window and then clicking the app window again).
Only after doing one of these two things are the views loaded.
Has anyone experienced the same behavior?
The application is developed in Swift only with UIKit and various Storyboards (without SwiftUI).
Thanks a lot to everyone.
I understand what happens. 
If it can be useful to anyone ... 
I use custom margins for my UIViewControllers. To calculate a margin, I use the minimum width of a scene as a variable.
I take this value in this way: 
var minSceneWidth:CGFloat = 400
scenes.forEach { windowScene in
if let w = windowScene.sizeRestrictions?.minimumSize.width {
if minSceneWidth > w {
minSceneWidth = w
}
}
}
Since the latest version of macOS the value "minimumSize.width" seems to be also "0" and this is not good for the calculation of my margins. 
Thank you all.

Best Practice for laying out UI Programmatically for both iPhone and iPad

Looking for a clean solution to laying out UI programmatically and having it look right on every device. I've tried extending CGFloat to scale numbers depending on the device
extension CGFloat {
func scale() {
// Modifies self by multiplies by the ratio between the initial screen size and the desired screen size
}
}
// usage
view.widthAnchor.constraint(equalToConstant: 20.scale())
I've also tried just creating two different sets of constraints for iPhone and iPad and activating them depending on which device the app is being run on, but this seems unnecessarily verbose.
How can I layout my UI so that it will work on all devices without hacky workarounds. Are there certain types of constraints I should be avoiding? (for example, instead of setting width/height constants, set them to a multiple of the screen's (or some other view's) width/height?)
EDIT: I don't want to over-explain my own situation, so let me re-ask the question. What are some best practices for setting constraints in an application designed for both iPhone and iPad. Is it bad practice to just check if the device is an iPad, and if it is, have constraints just for iPad, and, if not, have constraints for just iPhone.
Your question, as-is, cannot be answered...
Wha does your app do? If it's a photo slide-show, constrain an imageView to the full view and set its content mode to scale-fit. Voila! It "looks good" on all devices!
If your app is more complex than that, you will likely need to make use of all the types of constraints: elements relative to each other; equal to each other; relative/equal with constant adjustments; relative/equal with multiplier adjustments; etc. And you may want different layouts (not just different sizing) based on device+orientation, in which case you'll also want to take advantage of size-class-variations.
In addition, getting an app to "look right on every device" involves much, much, MUCH more than applying constraints.
Should the app use a Tab Bar?
A Navigation Bar?
A combination of them?
Neither?
Should it use text-buttons or image-buttons?
Should it adjust for accessibility and dynamic fonts?
Might it even have different functionality when running on a small screen vs a large screen?
In general, your first step should be hand-drawing every screen and UI element you expect to have - including the activity "flow" - with variations for sizes and orientations, so you are designing the best UI/UX from the beginning. At that point, you begin actual UI construction.
Keep in mind, there are people who can't produce a "Hellow World" app but make a very good living as "App Designers."
Having said all that, though... one approach you almost certainly should not take is:
view.widthAnchor.constraint(equalToConstant: 20.scale())
EDIT
Here is a quick example - based on this article: https://www.raywenderlich.com/1343912-adaptive-layout-tutorial-in-ios-12-getting-started
A simple weather app - constraints set so it looks similar on all iPhone and iPad models:
But, what happens when you rotate the phone?
Little, tiny cloud doesn't look so good. Add trait-variations / size-classes, and we can get:
And, to try and answer your edited question of: "Is it bad practice to just check if the device is an iPad..."?
Yes. It's recommended to design for trait-variations / size-classes so your app will look the way you want in all configurations (hopefully future-proof for the next device that comes out as well).
Is it bad practice to just check if the device is an iPad, and if it
is, have constraints just for iPad, and, if not, have constraints for
just iPhone.
If you're planning for Split-View support, that is definitely a no-go. The code for achieving and handling this is always going to be slightly verbose. There are no one-liners here to achieve what you want.
In your -[UIViewController viewDidLoad] method, you'd want to setup your views with the base trait collection available to the controller's view. This is your starting point.
As and when your App's windows are resized by the OS (imagine going for a fullscreen app in landscape mode to a split-sized app, occupying one-third of the screen): the -[UIViewController traitCollectionDidChange:] method is called on your View Controller. This is where you update your layout constraints.
You can, and should, encapsulate your layout logic in a single method and call it from -[UIViewController viewDidLoad] as well as -[UIViewController traitCollectionDidChange:].
In such a method, I do not recommend checking if the host device is an iPad. You specifically want to look at the active trait collection's horizontalSizeClass & verticalSizeClass properties to determine the values for your layout constraints.
For further information, I suggest you go through the following documentation:
1. https://developer.apple.com/documentation/uikit/uitraitcollection?language=objc
2. https://developer.apple.com/documentation/uikit/uitraitenvironment/1623516-traitcollectiondidchange?language=objc
The 2nd link has a simple example on how to check if you need to update your constraints.
I usually first get the size of the screen and then use percentages of the width/height for the frames
height = view.bounds.height
width = view.bounds.width
mybutton.frame = CGRect(x: width * 0.05, y: height * 0.02, width: width * 0.3, height: height * 0.1)

How can i make customizable views on IOS ? (i used style.id as a parameter on android view class constructor)

First I want to talk about what I exactly did on Android, and I am now trying to do the same on IOS :
I developed a library on Android which contains an activity, in that activity we have several type of views (labels, buttons ... etc).
These views are used as an entry point/inputs for my library that provides some services to other apps.
To be able to match the general style of the user app, I created my own View Classes, the user(of my library) needs to provide a style id to the activity before launching it, so it can be used when constructing the View.
Doing so, we are able to maintain the style of the app without the need to re-implement the views for every app that uses my library or making the user(final user on physical device) feels as if he just moved out to another app.
I am really a newbie on iOS and I recently started to learn it so i am not sure how to do it, can you please provide me some guidelines please?
On iOS there are so many ways to create views (xibs, Storyboards , SwiftUI, and programatically). I am not really sure where to start.
He's a sample of my code on Android :
class MyView(val context: Context, val themeId: Int) : View(context) {
init {
val wrappedContext = ContextThemeWrapper(context, themeId)
val mInflater = wrappedContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
mInflater.inflate(R.layout.my_view_layout, this, true)
}
}
This is a very broad question, but the short answer is "sure, it can be done."
I suppose a quick example might be:
I have a custom UIView with a UILabel centered in it, with 20-pts padding on all 4 sides.
I have 4 "themes" defined, defining the view's background color, the label's text color and the label's font style.
I write a custom init func to accept an Int "id" of 1 through 4
The library user could then instantiate it along the lines of let myView = MyView(with themeId: 2)
As this is a very broad question (topic), once you start developing your custom view you could come back if you run into specific coding questions.

How to handle a change in system level font size on iOS with React Native?

We have a situation where if a user decides to increase the "Larger Text" size in iOS Settings (see image below), it then messes up our view layouts in React Native, as the font size will now be larger.
For example here is a ListView row BEFORE changing the "Larger Text" setting in iOS system settings:
And here is the same row AFTER increasing the "Larger Text" setting:
What is best practice to handle such font scale increase in React Native ? This is obviously just one example, and there are other scenario's such as a View that only contains Text, but we do not want to increase the height of that View at runtime.
Appreciate anyone's advice on this, thanks!
You can do the following to block such changes (do it on your root js file):
Text.defaultProps.allowFontScaling = false;
you could use ITEM_SIZE * PixelRatio.getFontScale()

Resources