iPhone apps can run as iPad apps simply by adding iPad as a build target. However, without some optimization for the iPad, the app can be very user-unfriendly. Unfortunately, such iPad apps are not uncommon in the App Store.
In this article, I will introduce two minimal techniques to make "unfriendly" iPad apps decent with just a few adjustments.
Target
- Primarily an iPhone app
- Limited resources for iPad compatibility
- Uses common UI patterns (bottom tabs, stack navigation, lists, etc.)
struct ContentView: View {
var body: some View {
TabView {
NavigationStack {
List {
/* … */
}
.navigationTitle("Home")
.toolbar {
EditButton()
ShareLink(item: .init())
}
}
.tabItem { Label("Home", systemImage: "house") }
/* … */
}
}
}
Tip 1: Support Landscape Orientation
Many iPhone apps only support portrait orientation, and many iPad apps follow suit. iPad users generally use their devices in landscape orientation, and it can be frustrating to use apps that only support portrait mode. Unless there is a special reason, you should support landscape orientation.
Tip 2: Avoiding Unnecessary Horizontal Stretching of Content
A typical issue when adapting an iPhone app for the iPad in landscape orientation is that the content stretches too much horizontally, which looks unnatural. Ideally, you would redesign the app's UI specifically for the iPad, but that requires significant resources. Here, we introduce the approach of adding margins to the left and right sides of the content, which is commonly used in many apps.
struct AddSpace: ViewModifier {
func body(content: Content) -> some View {
GeometryReader {
content
.safeAreaPadding(.horizontal,
$0.size.width > 1100 ? 200 : 0)
}
}
}
//===================================
struct ContentView: View {
var body: some View {
TabView {
NavigationStack {
List {
/* … */
}
.modifier(AddSpace())
/* … */
}
/* … */
}
}
}
For Lists and ScrollViews, just adding padding on the sides can disrupt the appearance and gesture recognition area. We adjusted the left and right safe areas to add margins, allowing us to maintain a usable design without changing the iPhone app's layout.
Challenge of Split View
A typical design for landscape-oriented iPad apps includes using a sidebar for a Split View. If you simply replace a bottom tab with a sidebar for a selection-based Split View, it seems feasible to achieve a good result without allocating additional resources.
struct ContentView: View {
@State private var selection: String? = “Home”
var body: some View {
NavigationSplitView {
List(selection: self.$selection) {
Label("Home", systemImage: “house”).tag("Home")
Label("Favorite", systemImage: “star”).tag("Favorite")
Label("Setting", systemImage: "gearshape").tag("Setting")
Label("Help", systemImage: "info").tag("Help")
}
} detail: {
switch self.selection {
case "Home": /* … */
case "Favorite": /* … */
case "Setting": /* … */
case "Help": /* … */
default: /* … */
}
}
}
}
When you actually try this, it will require dedicating resources in various ways. For example:
- Considering different scenarios, such as when the window width is narrow
- Testing the behavior differences of content view instances when switching tabs (they don’t disappear with bottom tabs but disappear with Split View)
- Testing the operation of the navigation stack in the detail panel (toolbars and navigation links often behave unexpectedly)
- Starting unnecessary implementations like "adding sidebar items" or "automatic switching between bottom tabs and sidebar" due to getting carried away
If you have no experience with implementing Split View, I do not recommend diving into it casually.
Note 1: Disable Multiple Windows
Unlike iPhones, iPads can have multiple windows. To avoid issues from unexpected user inputs, disable multiple windows by setting "Enable Multiple Windows" to NO in Info.plist.
Note 2: Implementing iPad-Specific Code
You should use UIDevice.current.userInterfaceIdiom.
struct ContentView: View {
var body: some View {
switch UIDevice.current.userInterfaceIdiom {
case .phone: Text(“I am iPhone")
case .pad: Text(“I am iPad")
default: EmptyView()
}
}
}
Top comments (0)