How to adapt your View for each device

Gaetano Torella
5 min readApr 6, 2022

--

Written by: Mirko D'Agnese and Gaetano Torella

How many times have you developed an app and realized that turning the iPhone in landscape mode, or changing the device to a bigger one nothing went as you planned?

During the development of an app, one of the main aspects you have to pay attention to is making it for each device, from the smallest one like Apple Watch to the biggest like the iMac.

https://developer.apple.com/

Multi-Platform Apps

To automatically adapt each element of your app to each device you can use:

UIScreen.main.bounds

UIScreen is a class by UIKit that give you information about the screen of your device

UIScreen.main.bounds.width → returns the width of the device screen

UIScreen.main.bounds.height → returns the height of the device screen

  • You can use it to resize the width of a column in LazyVGrid:
let columns = [
GridItem(.fixed(UIScreen.main.bounds.width * 0.45)),
GridItem(.fixed(UIScreen.main.bounds.width * 0.45))
]
...LazyVGrid(columns: columns) {
ForEach(arrayCharacter) { chara in
GridView(character: chara)
}
}
  • Otherwise you can use to adapt the size of a custom row in a list:
HStack(spacing: 30){
Image("placeholder")
.resizable()
.scaledToFit()
Text(character.name)
.font(.title3)
.bold()
}
.frame(width: UIScreen.main.bounds.width * 0.90, height: UIScreen.main.bounds.height * 0.12)

In this way each, element of your view automatically resizes itself according to the device size.

Landscape and Portrait Mode

If you don’t want to waste too much time in customizing the landscape mode of your app or you are not interested in landscape, you can simply lock the interface orientations of the app to portrait mode changing the values of Supported Interface Orientations in the Info.plist (you can find it in yourproject.xcodeproj → TARGETS → yourproject(iOS) → Build Settings)

Otherwise, if you choose to customize your view in different ways for landscape and portrait mode you have to pay attention to some aspects.

You can use this constant to identify the kind of device and the interface orientation:

let idiom = UIDevice.current.userInterfaceIdiom

idiom identify the kind of device you are working on:

  • .phone
  • .pad
  • .mac
  • .tv
  • .carplay
@Environment(\\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?

horizontalSizeClass is a variable of SwiftUI and has 2 attributes:

  • .regular → identify the landscape mode*
  • .compact → identify the portrait mode**

* For iPad both portrait and landscape have only .regular attribute

** For iPhone before iPhone X and SE series both portrait and landscape have only .compact attribute

For example, if you want to show a different number of columns in your grid view for portrait and landscape you can use Conditional modifiers:

import SwiftUIstruct ContentView: View {

@Environment(\\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?

let columnsCompact = [
GridItem(.fixed(UIScreen.main.bounds.width * 0.45)),
GridItem(.fixed(UIScreen.main.bounds.width * 0.45))
]

let columnsRegular = [
GridItem(.fixed(UIScreen.main.bounds.height * 0.2)),
GridItem(.fixed(UIScreen.main.bounds.height * 0.2)),
GridItem(.fixed(UIScreen.main.bounds.height * 0.2)),
GridItem(.fixed(UIScreen.main.bounds.height * 0.2))
]

@State var array = [1,2,3,4,5]

var body: some View {
LazyVGrid(columns: horizontalSizeClass == .compact ?
columnsCompact : columnsRegular) {
ForEach(array, id: \\.self) { index in
VStack {
Image("placeholder")
.resizable()
.scaledToFill()
Text("Name and surname")
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewInterfaceOrientation(.landscapeRight)
}
}
Portrait & Landscape Mode

⚠️ PAY ATTENTION

The .compact and .regular attributes for horizontalSizeClass are not the same for each device. To be sure of which modifier you have to use check the HIGs on this link: https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/adaptivity-and-layout/

NavigationView & SideBar

If you develop an app with a NavigationView inside, when you turn the device into landscape mode, SwiftUI automatically adapts the view in the best way for your device screen.

For devices that in landscape mode have the .regular attribute of horizontalSizeClass, SwiftUI sets the NavigationView as SideBar, as shown:

It happens for iPad and .regular iPhone in landscape mode

NavigationView has a modifier to set the layout style:

NavigationView {
...
}
.navigationViewStyle(_:)

Unlike the other view modifiers that are usually applied inside the NavigationView, .navigationViewStyle(_:) must be applied to the nav view itself.

.navigationViewStyle(_:) is a modifier of SwiftUI and has 3 Type Properties:

  • .automatic: the default navigation view style in the current context of the view being styled.
  • .columns: the view will be presented always as columns wherever that’s possible.
  • .stack: it forces the navigation view to show contained views one at a time, just like it happens in iPhone by default.

In case you want to customize the landscape layout for different devices you can add this extension to View:

extension View {
@ViewBuilder func phoneOnlyStackNavigationView() -> some View {
if UIDevice.current.userInterfaceIdiom == .phone {
self.navigationViewStyle(.stack)
} else {
self.navigationViewStyle(.automatic)
//you can also replace this line with "self" because it returns automatically the default value

}
}
}

In this way the NavigationView layout on iPhone is forced to .stack, meanwhile, on iPad, it will be automatically set. You can also modify the values of navigationViewStyle as you prefer.

--

--

Gaetano Torella

Student at Apple Developer Academy @Unina Federico Il // Automation Engineering Student @Università degli Studi di Napoli Federico II