I have 2 HStack
‘s using geometry reader to split them evenly into 2 sections that are embedded into a VStack
, I am trying to create a layout similar to the first below image (landscape mode on iPad).
However, I am struggling to get the HStack
‘s to line up like a grid meeting in the middle. I also have a NavigationView
sidebar and which can be presented alongside the HStack
s so ideally the 2 images would change widths but keep their height without squashing or stretching the images. I have tried to do this using clipped()
.
The second image below is what I am getting when I run my code. I have replaced the images in this example to SFSymbol to make it easier to debug.
This is the NavigationView
sidebar that is being called in my ContentView
:
struct SideBar: View {
var body: some View {
NavigationView {
List {
NavigationLink(destination: DetailView()) {
Label("Products", systemImage: "printer")
}
Label("Comparison", systemImage: "simcard.2")
Label("Search", systemImage: "magnifyingglass")
}
.listStyle(SidebarListStyle())
.navigationTitle("Navigation")
DetailView()
}
}
}
This is the main view that holds the content:
struct DetailView: View {
let title = "This is a title"
let paragraph = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
let image = "dot.squareshape.fill"
let intPadding: CGFloat = 10
let extPadding: CGFloat = 40
var body: some View {
VStack(spacing: 0){
GeometryReader { geometry in
HStack(alignment: .top, spacing: 0){
Image(systemName:image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geometry.size.width / 2)
.clipped()
VStack(alignment: .leading) {
Text(title)
.font(.custom("Avenir-Heavy", size: 30))
.multilineTextAlignment(.leading)
.padding(.leading, intPadding)
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
.padding(.leading, intPadding)
.padding(.trailing, extPadding)
}
.frame(width: geometry.size.width / 2)
}
}
GeometryReader { geometry in
HStack(alignment: .top, spacing: 0){
Text(paragraph)
.font(.custom("Avenir", size: 16))
.multilineTextAlignment(.leading)
.lineSpacing(10)
.frame(width: geometry.size.width / 2)
.padding(.top, intPadding)
.padding(.trailing, intPadding)
.padding(.leading, extPadding)
Image(systemName:image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geometry.size.width / 2)
.offset(x: -intPadding)
.clipped()
}
}
}
}
}
EDIT:
The black squares are representing where images are going to go, I am not putting black squares. So the idea of the squashing and stretching mentioned above is supposed to look like the below image, so it doesn’t actually stretch or squash the image just the bounding box:
2
Answers
Here is a demo of possible approach – use
Color.clear
, as it fills everything available equally, with content in overlays.Prepared with Xcode 12.1 / iOS 14.1
Update: full-screen variant
The biggest challenge for this layout is height. On each HStack row you have variable multi-line text (which can change size depending on accessibility, fonts, etc). If the text is longer than expected or user has increased it, the layout will not hold.
To derive the row height, you can use an aspect ratio to set the height of the Image frames and variable Text blocks. This locks the row height for both img/text so the image corners are always touching no matter what the screen width or text length. Longer text will end up getting an ellipse, or you can use a ScrollView and set its height the same as the Image derived aspectHeight.
The code below cleans up all the padding/offsets causing the horizontal spread issues and uses an image aspect ratio (16×9). Assuming the images are pretty much standard sizes, or use whatever you like (4×6, etc). Note the images weren’t "zooming" correctly using "fit", use aspectRatio.fill to stretch the image out from its center equally.
If you don’t want to pre-define the image aspect ratio and need the images to have pixel perfect aspect, Swift can pre-load the image files to get the aspect:
SwiftUI: How to find the height of an image and use it to set the size of a frame
Note: LazyVStack has a grid column feature but requires iOS14+ so I stuck with VStack/HStack.