You do not want any GeometryReaders to realize this structure. You are able to do it merely with some overlays and orientation-based stacks.
For the buttons to be aligned with the blue digicam preview, they only have to be in the identical stack. The one distinction is whether or not it is an HStack
or a VStack
primarily based on orientation.
Within the instance beneath, I stored it easy by checking the verticalSizeClass
, but it surely may be finished utilizing ViewThatFits
possibly, though it might turn into unnecessarily difficult.
The buttons are additionally equally organized in a horizontal or vertical stack primarily based on orientation. If wanted, their container may very well be a ScrollView
to accommodate extra buttons.
Relying on the orientation, it’s worthwhile to ignore the suitable secure areas, so the digicam preview can fill the area properly.
Extra parts may be added over the blue preview as overlays (see the grid strains and the shut button as examples within the code beneath).
import SwiftUI
struct CameraUIRootView: View {
//State values
@State non-public var showCameraPreview = false
//Physique
var physique: some View {
VStack {
Button {
withAnimation {
showCameraPreview.toggle()
}
} label: {
Label("Take image", systemImage: "digicam.fill")
}
.buttonStyle(.borderedProminent)
}
.fullScreenCover(isPresented: $showCameraPreview) {
CameraUIPreview()
}
}
}
struct CameraUIPreview: View {
//Atmosphere values
@Atmosphere(.verticalSizeClass) var verticalSizeClass
@Atmosphere(.dismiss) var dismiss
//Helper computed property for detecting panorama orientation
non-public var isLandscapeOrientation: Bool {
verticalSizeClass == .compact
}
//State values
@State non-public var showGridLines = false
//Physique
var physique: some View {
Group {
if isLandscapeOrientation {
HStack(spacing: 0) {
//Digital camera preview
cameraPreview
//Controls
CameraUIControls()
}
}
else {
VStack(spacing: 0) {
//Digital camera preview
cameraPreview
//Controls
CameraUIControls()
}
}
}
.ignoresSafeArea(.container, edges: isLandscapeOrientation ? [.leading, .vertical] : [.top]) // <- Elective: use [.bottom] when you do not wish to push the preview into the highest secure space or go away clean [], which can trigger points with respecing the side ratio relying on machine
.persistentSystemOverlays(.hidden)
.statusBarHidden()
.body(maxWidth: .infinity, maxHeight: .infinity, alignment: isLandscapeOrientation ? .main : .prime)
}
non-public var cameraPreview: some View {
Colour.blue
.aspectRatio(isLandscapeOrientation ? 16.0/9.0 : 9.0/16.0, contentMode: .match)
//Shut preview button
.overlay(alignment: .topTrailing) {
Button {
withAnimation {
// showCameraPreview.toggle()
dismiss()
}
} label: {
Textual content("Shut")
}
.tint(.white)
.padding(30)
}
// Shutter button
.overlay(alignment: isLandscapeOrientation ? .trailing : .backside) {
Button {
showGridLines.toggle()
} label: {
Picture(systemName: "digicam")
.imageScale(.massive)
.padding()
}
.tint(.white)
.body(width: 80, peak: 80)
.background(.white.gradient.opacity(0.4), in: Circle())
.padding()
}
//Gridlines overlay
.overlay {
CameraUIGridLines()
}
}
}
struct CameraUIControls: View {
//Atmosphere values
@Atmosphere(.verticalSizeClass) var verticalSizeClass
//Helper computed property for detecting panorama orientation
non-public var isLandscapeOrientation: Bool {
verticalSizeClass == .compact
}
//Physique
var physique: some View {
Group {
if isLandscapeOrientation {
VStack {
controls
}
}
else {
HStack {
controls
}
}
}
.padding()
}
@ViewBuilder
non-public var controls: some View {
Group {
//Flash button
Button {
//...
} label: {
Picture(systemName: "bolt.fill")
.padding()
}
Spacer()
//Gridlines button
Button {
//...
} label: {
Picture(systemName: "grid")
.padding()
}
Spacer()
//Macro mode button
Button {
//...
} label: {
Picture(systemName: "digicam.macro")
.padding()
}
Spacer()
//Metering mode button
Button {
//...
} label: {
Picture(systemName: "digicam.metering.heart.weighted")
.padding()
}
Spacer()
//Flip digicam button
Button {
//...
} label: {
Picture(systemName: "digicam.rotate")
.padding()
}
}
.background(.grey.gradient.opacity(0.2), in: Circle())
.tint(.major)
}
}
struct CameraUIGridLines: View {
//Physique
var physique: some View {
ZStack {
HStack {
gridLines
}
VStack {
gridLines
}
}
}
non-public var gridLines: some View {
Group {
Spacer()
divider
Spacer()
divider
Spacer()
}
}
non-public var divider: some View {
Divider()
.background(Colour.white) //Divider colour
}
}
//Preview
#Preview("Root view") {
CameraUIRootView()
}
#Preview("Digital camera preview") {
CameraUIPreview()
}
#Preview("Controls") {
CameraUIControls()
}