I have a view controller with some nested views in portrait mode, but I need to know if its posible to generate a variation on landscape where I only have one image (deleting all my elements that I have in portrait view) or I need to create another view controller for this case.
you can change that in code using traitcollection
For your case
you can use below condition which presents landscape orientation
if traitCollection.verticalSizeClass == .compact {
labelName.isHidden = true // hide label
textfield.isHidden = true // hide text
imageName.isHidden = false // unhide image
}
Note: you have also traitcollection.horizontalSizeClass and it can be .compact or .regular according to which orientation of the device you want to edit and the type of device you are working on.
traitcollection options for different devices
Related
I'm using the following layout as a custom popup UIView in Xcode 13 (the white background is transparent):
When the screen orientation is changed to landscape mode, the constraint at the top and bottom are still 100pts. Because of that the middle part (yellow, UIView with UIStackView with UITableView,... inside) is really small and a warning shows up in console about the top (red) and bottom (blue) bar:
Unable to simultaneously satisfy constraints.
I know what this warning means. To fix it I created the following function...
private let constraintPortrait:CGFloat = 100
private let constraintLandscape:CGFloat = 10
private func fixConstraints() {
if (UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight) && UIDevice.current.userInterfaceIdiom == .phone {
topConstraint.constant = constraintLandscape
bottomConstraint.constant = constraintLandscape
} else {
topConstraint.constant = constraintPortrait
bottomConstraint.constant = constraintPortrait
}
}
... and call it both in viewDidLoad and viewDidLayoutSubviews. This was working great but every now and then the warning still popped up, so I added prints to viewDidLoad,... and noticed that the warning is actually printed before my constraint fix is called. I renamend viewDidLayoutSubviews to viewWillLayoutSubviews (UIViewController lifecycle here) and Abracadabra!, the warning was gone.
People usually recommend to use viewDidLayoutSubviews when you want to do stuff after the device was rotated but hardly ever mention viewWillLayoutSubviews and while searching for a reason for that I found this answer, saying not to use the latter to change constraints because it might cause another autolayout pass.
Question:
What should I use instead to prevent the conflicts (without changing the fixed constraints for portrait mode!)? Is there a way to change the top and bottom constraint automatically and solely in the Interface Builder, without using any code and only when actually necessary (-> always keep the 100pts in portrait mode, even with a long table, but switch to 10pts instantly in landscape mode when there isn't enough space)?
viewWillLayoutSubviews is correct. Any layout changes you perform here, including changes of constraints, will be animated automatically in coordination with the rotation animation.
But how will you know that this call to viewWillLayoutSubviews is due to rotation? Implement this method:
https://developer.apple.com/documentation/uikit/uicontentcontainer/1621466-viewwilltransition
Or, on an iPhone, this method:
https://developer.apple.com/documentation/uikit/uicontentcontainer/1621511-willtransition
I like the former because it works on both iPad and iPhone. These are called before viewWillLayoutSubviews, so you can set an instance property to signal to yourself that the size is officially changing. You can work out what's happening by comparing the bounds size height to the bounds size width, and change the constraints accordingly.
I am building a calculator app and have it working in portrait mode. However, I want the user to have more options/functions when they switch to landscape mode. In other words, the entire layout would have to change as there will be more buttons in landscape mode compared to portrait mode.
I have managed to do this using the installed property under the attribute inspector. Here are the problems I am running into:
When I run the app in portrait mode and rotate it to landscape, I will show the buttons but they are overlapped. Here is a demo(Just wait for the gif to restart):
When I run the app in landscape mode, it displays all the buttons correctly, but as soon as I rotate it again to portrait and then back to landscape, the same problem as #1 happens. Here is a demo (Just wait for the gif to restart):
What am I doing wrong? How can I resolve this?
Here's what I've been doing since iOS9. Complete code, does not assume you are using IB (I actually think you should not) and should help you with how auto layout works. BEWARE, this does assume some knowledge of auto layout and Swift. This also deals with a very simple example of a UIButton.
First, declare two arrays of constraints, typically in the view controller:
var landscape = [NSLayoutConstraint]()
var portrait = [NSLayoutConstraint]()
var myButton = UIButton()
Next, set up your constant constraints and set the active:
myButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myButton)
myButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10).isActive = true
myButton.heightAnchor.constraint(equalToConstant: 40).isActive = true
myButton.widthAnchor.constraint(equalToConstant: 200).isActive = true
So far, so good. You've created a button, added it to the VC view, declared it's size as 40x200. You've also declared it to be anchored to the parent's safeAreaLayoutGuide top anchor.
Now, it's time for the "magic". You wish to move this UIButton from the bottom to the right depending on orientation. (Please note, while certain overrides will work on iPhones, iPads, when full screen, will always have a "Regular" size class - in full screen - no matter the orientation!)
The first thing you need is to put the remaining constraints into the arrays you declared.
portrait.append(myButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10))
landscape.append(myButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10))
Now you have s single constraint to activate. It can be more, remember we are talking arrays. This is simple, just deactivate and activate as needed:
func changeConstraints() {
if isInPortrait {
NSLayoutConstraint.deactivate(landscape)
NSLayoutConstraint.activate(portrait)
} else {
NSLayoutConstraint.deactivate(portrait)
NSLayoutConstraint.activate(landscape)
}
}
You can even animate things is you wish! Finally, let's tap into the VC life cycle. I'm very sure there are other (and better) ways, but keep in mind what I mentioned earlier - in full screen, an iPad will always have a Regular size class. So I prefer to dig just a bit deeper. I tend to use three variables - you probably need less.
var initialOrientation = true
var isInPortrait = false
var orientationDidChange = false
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if initialOrientation {
initialOrientation = false
if view.frame.width > view.frame.height {
isInPortrait = false
} else {
isInPortrait = true
}
changeConstraints()
} else {
if view.orientationHasChanged(&isInPortrait) {
changeConstraints()
}
}
}
I also have an extension to UIView:
extension UIView {
func orientationHasChanged(_ isInPortrait:inout Bool) -> Bool {
if self.frame.width > self.frame.height {
if isInPortrait {
isInPortrait = false
return true
}
} else {
if !isInPortrait {
isInPortrait = true
return true
}
}
return false
}
}
I think this should describe most of my code. Basically, (a) do not use UIDevice nor (b) use viewWillTransition(toSize:) and check a size class when working with a iPad. (They will have a size class change - albeit differently on devices depending on size, but only when in split screen mode. At least for now!)
Pretty much, use (a) view willLayoutSubviews - you can use view DidLayoutSubviews but I prefer doing things at the earliest point possible - and then (b) check the actual screen size.
I have designed my app initially for the iPad and am now wanting to add functionality for an iPhone too. Is there a way to check what the device being used is, and then display a view accordingly?
Structured English:
IF current_device == iPhone THEN
DISPLAY iPhoneView
ELSE IF current_device == iPad THEN
DISPLAY iPadView
If possible I also want the iPad view to only be available horizontally and then the iPhone view to only be available vertically if possible.
What you are looking for are Size Classes.
To read current horizontal size class in SwiftUI view you can refer to the environment value of horizontalSizeClass
#Environment(\.horizontalSizeClass) var horizontalSizeClass
Then use it like this in your SwiftUI View:
var body: some View {
if horizontalSizeClass == .compact {
return CompactView() // view laid out for smaller screens, like an iPhone
} else {
return RegularView() // view laid out for wide screens, like an iPad
}
}
It is worth noting that not all iPhones are compact horizontally, and compact size class is present on iPad while in multitasking configuration. You will find all possible combinations here under the Device Size Classes and Multitasking Size Classes sections.
Some articles that may be helpful
How to create different layouts using size classes
Changing a view’s layout in response to size classes
Alternatively you could set an individual threshold based on the devices height (or width) using a variable like this:
#State var isLargeDevice: Bool = {
if UIScreen.main.bounds.height > 800 {
return true
} else {
return false
}
}()
I need to make a different layout for iPad on landscape and portrait orientations, but size classes on iPad are always regular/regular. Recommended way to adapt interfaces by Apple is to use adaptive layout with size classes, but on iPad there is no difference between portrait and landscape mode.
Is there any way to present different layout on portrait and landscape orientation on iPad without detection device orientation?
As a example, on iPhone (compact/regular) will be shown as:
And on iPad, in landscape (regular/regular) will be shown as:
So, the goal is show the iPhone layout on iPad when is in portrait mode.
Thanks!
You can force apply horizontal size class depending on orientation. If I correctly understood your goal here is a demo of possible approach (tested with Xcode 12.1 / iOS 14.1):
struct DemoViewSizes: View {
#Environment(\.horizontalSizeClass) var horizontalSizeClass
#State private var orientation = UIDevice.current.orientation
var body: some View {
Text("root_view_here")
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
orientation = UIDevice.current.orientation
}
.environment(\.horizontalSizeClass, orientation == .portrait ? .compact : .regular)
}
}
I have a UISplitViewController in my application (MvvmCross / Xamarin iOS) and for some reason I cannot get the content to respect the dimensions of the available view areas.
In the situation shown in the screenshot the master view is hosting a UIViewController with a TableView inside. All the layouts are done with constraints and work fine on their own when running in an iPhone emulator.
As soon as I switching to running on an iPad some custom code I have in my presenter shows this same view in the master panel of a UISplitViewController but in this situation the constraints seem to be ignored and I end up with a view that looks like this:
As you can see the right hand side of the table cell is now way off the viewable area of the master panel of the UISplitViewController.
Both the UITableView and the UITableCell both use View.Frame as their initial size (I've tried View.Bounds as well).
How can I get the cells and / or table to respect the bounds of the UISplitViewController available space?
Thanks to Cheesebarons question I found my solution (cause).
I have a set of methods in a helper class that I use to generate my "default" UIViews.
One of these methods creates my default UITableView:
public static UITableView CreateDefaultTableView(CGRect rect, UITableViewStyle style)
{
var tv = new UITableView(rect, style)
{
AutoresizingMask = UIViewAutoresizing.FlexibleHeight,
SeparatorStyle = UITableViewCellSeparatorStyle.SingleLine,
SeparatorColor = IosConstants.DefaultTableSeparatorColor,
BackgroundColor = IosConstants.DefaultViewBackgroundColor
};
return tv;
}
Changing:
AutoresizingMask = UIViewAutoresizing.FlexibleHeight,
To:
AutoresizingMask = UIViewAutoresizing.All,
Schoolboy error!