Left and right padding (not leading and trailing) in SwiftUI

Viewed 14339

A padding modifier in SwiftUI takes an EdgeInsets, eg

.padding(.leading, 8)

However, there are only leading and trailing EdgeInsets, and not left and right. This presents a problem because not everything in RTL languages should necessarily be reversed from the way it's displayed in LTR languages.

Is there a way to achieve the same or similar effect as the padding modifier, which forces the padding to be on left or right side, regardless of directionality of the language?

3 Answers

Here is a simplified demo of possible approach - use extension with injected fixed-sized Spacer at each side.

Prepared & tested with Xcode 13 / iOS 15

enum Side: Equatable, Hashable {
    case left
    case right
}

extension View {
    func padding(sides: [Side], value: CGFloat = 8) -> some View {
        HStack(spacing: 0) {
            if sides.contains(.left) {
                Spacer().frame(width: value)
            }
            self
            if sides.contains(.right) {
                Spacer().frame(width: value)
            }
        }
    }
}

demo of usage

var body: some View {
  TextField("Last Name", text: $nameLast)
    .textFieldStyle(.roundedBorder)
    .padding(sides: [.left], value: 20)
}

demo

Use @Environment(\.layoutDirection) to get the current layout direction (LTR or RTL) and use it to flip .leading and .trailing as needed.

Here’s a ViewModifier that wraps all that conveniently:

enum NoFlipEdge {
    case left, right
}

struct NoFlipPadding: ViewModifier {
    let edge: NoFlipEdge
    let length: CGFloat?
    @Environment(\.layoutDirection) var layoutDirection
    
    private var computedEdge: Edge.Set {
        if layoutDirection == .rightToLeft {
            return edge == .left ? .trailing : .leading
        } else {
            return edge == .left ? .leading : .trailing
        }
    }
    
    func body(content: Content) -> some View {
        content
            .padding(computedEdge, length)
    }
}

extension View {
    func padding(_ edge: NoFlipEdge, _ length: CGFloat? = nil) -> some View {
        self.modifier(NoFlipPadding(edge: edge, length: length))
    }
}

Use it like you would the standard padding modifiers:

Text("Text")
    .padding(.left, 10)

For me it was

VStack(alignment: .center) {
                    Text("Car")
                        .font(.body)
                        .foregroundColor(.black)
                }
                .padding(.trailing, 5)
Related