read

This is a list of short SwiftUI snippets that I will be updating continuously. They are not hard, but SwifUI has 99+ modifers, working for all views..

Resizable image, fits in frame

Image(systemName: "umbrella")
    .resizable()
    .aspectRatio(contentMode: .fit)
    .frame(width: 50, height: 50)

Increase hit area, including transparent area

HStack {
    Text("When should you test yourself?")
    Spacer() // Without `contentShape`, this space is not hittable
}
.contentShape(Rectangle()) // Define hit testing
.onTapGesture { ... }

Increase hit area for Button

For Button, you have to modify the label view, which means you can’t use the convenient Button("The label").

Button {
} label: {
    Text("The label")
        .frame(maxWidth: .infinity, maxHeight: .infinity) // Fill it
}

Custom Modifier

struct MyModifier: ViewModifier {
    func body(content: Content) -> some View {
        // Do something with the content view
    }
}

// Convenient extension on View
extension View {
    func myModifier() -> some View {
        self.modifier(MyModifier())
    }
}

Dismiss modal view

@Environment(\.dismiss) var dismiss

var body: some View {
    Button("Trigger dismiss") { 
        dismiss() 
    }
}

It works for presenting modal sheet, and also for popping back in a navigation view.

Dismiss modal view 2 – the explicit way

In the presenter,

@State private var isPresented = false // Declare a state

var body: some View {
    Button("Trigger present") { 
        isPresented = true
    }
    .sheet(isPresented: $isPresented) { // The modal sheet uses the binding
        ChildView(isPresented: self.$isPresented) // Pass the binding to child view
    }
}

In the ChildView,

@Binding var isPresented: Bool // Declare the binding

var body: some View {
    Button("Dismiss this modal view") { 
        isPresented = false // Will change the state in parent (source of truth)
    }
}

Full screen presentation

#if os(iOS)
.fullScreenCover(isPresented: $isPresented) {
    Text("This is full screen only on iOS")
}
#else
.sheet(isPresented: $isPresented) {
    Text("Thi is sheet for others")
}
#endif

FIX Text truncating when it shouldn’t

This usually happens when the Text is in VStack contained in a ScrollView.

Text("Why the hell is this text truncated without reason?")
    .fixedSize(horizontal: false, vertical: true) // Workaround magic

Read next on the magical modifier.

What is fixedSize?

This modifier is difficult to understand.

In the above example we have .fixedSize(horizontal: false, vertical: true). That is telling the text view to maintain its ideal height (vertical true).

So for a long text, the text view can span over multiple lines, reaching it’s ideal height.

If it is horizontal true, then you’re telling the text view to maintain it’s ideal width – that is 1 long line!

Select an item using Picker in a Form

Picker can show the currently selected item, but somehow only works if the selection is Int/String.

// Assuming we have an enum to represent the list of items
enum Side: CaseIterable {
    case wife, husband
}

@State var sideIndex = 0

Picker("Whose side you taking?", selection: $sideIndex) {
    ForEach(Side.allCases.indices) {
        Text(Side.allCases[$0].description)
    }
}

UPDATE: It seems to work now, as of 2024, without needing the index approach.

enum Side: CaseIterable, CustomStringConvertible { ... }

@State var side: Side = .husband

Picker("Whose side you taking?", selection: $side) {
    ForEach(Side.allCases, id: \.self) { value in
        Text(value.description)
            .tag(value)
    }
}

Environment (key & value)

struct MyEnvironmentKey: EnvironmentKey {
    static var defaultValue: CGFloat { 0 }
}
extension EnvironmentValues {
    var foo: CGFloat {
        get { self[MyEnvironmentKey.self] }
        set { self[MyEnvironmentKey.self] = newValue }
    }
}

ParentView().environment(\.foo, 1.23) // Parent set the env

@Environment(\.foo) var foo // Children declare and use

EnvironmentObject

class MyModel: ObservableObject {
    @Published var foo: String = "a"
}

struct ParentView: View {
    @ObservedObject var model = MyModel()
    var body: some View {
        VStack {
            ChildView()
            ChildView()
        }.environmentObject(model)
    }
}

struct ChildView: View {
    @EnvironmentObject var model: MyModel
}

Text with scalable font

Text("This will scale")
    .font(.title)
    .minimumScaleFactor(0.5)
    .lineLimit(1)

Common PreviewProvider

MyView()
    .previewDevice(PreviewDevice(rawValue: "iPhone 12 Pro Max"))
    .preferredColorScheme(.dark)
    .environment(\.sizeCategory, .extraSmall)
    .environment(\.sizeCategory, .accessibilityExtraExtraExtraLarge)

Extract into private methods

The cool thing with SwiftUI is that you can build the UI in a modular way. While it can become deeply nested easily, you can use the trick to encapsulate your code. Highlight block of code > Right click > Refactor > Extract to Method.

@ViewBuilder fileprivate func myModularView() -> some View {
    if size == .systemSmall {
        VStack { ... }
    } else {
        HStack() { ... }
    }
}

The generated code (as of 2021) is still not great. If you encounter the following error, make sure to have @ViewBuilder like the sample above.

Error: Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type

Ignore safe area, but want child to respect safe area

var body: some View {
    GeometryReader { proxy in
        VStack() {
            Spacer()
            Text("Create the safe area with a padding")
                .background(Color.blue)
                .padding(.bottom, proxy.safeAreaInsets.top)
        }
        .edgesIgnoringSafeArea(.bottom)
        .background(Color.red)
    }
}

Stroke & fill a shape

var body: some View {
    RoundedRectangle(cornerRadius: 30)
        .strokeBorder(.black, lineWidth: 10)
        .background(RoundedRectangle(cornerRadius: 30).foregroundColor(Color.green))
        .frame(width: 200, height: 100)
}

Image

@samwize

¯\_(ツ)_/¯

Back to Home