George Garside Blog

A place of many ramblings about macOS and development. If you find something useful on here, it's probably an accident.

Most SwiftUI view modifier functions take nil as a parameter that ‘turns off’ the modifier. For example, padding(_:_:) can take nil as the first argument, which disables the padding modifier. This is distinct to disabling all the padding with padding(0).

if nil the specified or system-calculated mount is applied to all edges.

https://developer.apple.com/documentation/swiftui/view/padding(_:_:)

However, this isn't always possible. For example, id(_:) takes nil as a distinct ID separate to not calling the view modifier. Another example is using if #available(iOS 15, *) {…} to call different view modifiers — this can't be done inline.

if

Add the following in an extension View {…}:

/// Closure given view if conditional.
/// - Parameters:
///   - conditional: Boolean condition.
///   - content: Closure to run on view.
@ViewBuilder func `if`<Content: View>(_ conditional: Bool, @ViewBuilder _ content: (Self) -> Content) -> some View {
	if conditional {
		content(self)
	} else {
		self
	}
}
Code language: Swift (swift)

This adds an if view modifier which can contain any code you desire, including if #available checks.

Pass a conditional as the first argument, then a ViewBuilder as a second argument. If the conditional is true, the view modifiers in the closure will be applied. Apply your modifiers to $0 in the closure.

Text("foo")
	.if(x == 2) {
		$0.bold()
	}Code language: JavaScript (javascript)

The default for the first argument is true, so you can omit this if you want to add your own code for switching view modifiers. For example, an if #available check:

List { … }
	.if {
		if #available(iOS 15, *) {
			$0.refreshable { … }
		} else {
			$0
		}
	}Code language: PHP (php)

if else

A modification to the original if function lets you pass an else ViewBuilder too.

/// Closure given view if conditional.
/// - Parameters:
///   - conditional: Boolean condition.
///   - truthy: Closure to run on view if true.
///   - falsy: Closure to run on view if false.
@ViewBuilder func `if`<Truthy: View, Falsy: View>(
	_ conditional: Bool = true,
	@ViewBuilder _ truthy: (Self) -> Truthy,
	@ViewBuilder else falsy: (Self) -> Falsy
) -> some View {
	if conditional {
		truthy(self)
	} else {
		falsy(self)
	}
}
Code language: Swift (swift)

Call this with another trailing closure for the false case of the conditional.

Text("georgegarside.com")
	.if(x == 73) {
		$0.bold()
	} else: {
		$0.italic()
	}Code language: JavaScript (javascript)

if let

Similarly, you can reproduce if let inside view modifiers too with a minor adaptation of the previous extension. If the optional is none, the closure is not executed, otherwise the closure is executed with the unwrapped optional passed as the second argument.

/// Closure given view and unwrapped optional value if optional is set.
/// - Parameters:
///   - conditional: Optional value.
///   - content: Closure to run on view with unwrapped optional.
@ViewBuilder func iflet<Content: View, T>(_ conditional: Optional<T>, @ViewBuilder _ content: (Self, _ value: T) -> Content) -> some View {
	if let value = conditional {
		content(self, value)
	} else {
		self
	}
}Code language: Swift (swift)

Access the view with $0 and the unwrapped optional value with $1.

Text("@grgarside on Twitter")
	.iflet(socialFont) {
		$0.font($1)
	}Code language: Swift (swift)

Leave a Reply

No comments