Make a placeholder view in SwiftUI with redacted()

⋅ 4 min read ⋅ SwiftUI

Table of Contents

In iOS 14, SwiftUI has a new modifier to redact any content. You apply it to any view with a new view modifier, redacted(reason:). This modifier required a reason modifier, which tells SwiftUI the reason for this redaction. Right now, Apple provides only one reason, .placeholder.

Here’s how it looks:

Text("Hello, SwiftUI!")
.redacted(reason: .placeholder)
Redact with placeholder style
Redact with placeholder style

As the name implies, placeholder reason redact content in a way to be used as a placeholder before the actual content is loaded.

Since Apple design RedactionReasons as an option set, I expected more reason that we can use and combine in the future. Something like .sensitive reason with pixelation effect or .animated which produce a shimmering effect would be a nice add up.

public struct RedactionReasons : OptionSet

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

Complex View

Redaction reasons pass along to all child views. That's mean you can call it on a parent view, and all subviews would get the same reasons.

struct LandmarkView: View {    
var body: some View {
VStack(alignment: .leading) {
Image("bigsur")
.resizable()
.frame(width: 100, height: 100)
.clipShape(Circle())
Text("Big Sur \(Image(systemName: "sun.dust"))")
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.blue)
Text("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy").font(.body)
Button("Bookmark", action: {
print("Bookmark")
})
}
.padding()
}
}

Calling .redacted on LandmarkView would redact all of its subviews.

LandmarkView().redacted(reason: .placeholder)
Redaction reasons pass along to all child views.
Redaction reasons pass along to all child views.

Opt-out

If you don't want some view to be redacted, you can opt-out by using .unredacted() which removes any redaction reason that is applied to the view.

Text("Big Sur \(Image(systemName: "sun.dust"))")
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.blue)
.unredacted()

Add .unredacted() to Text will produce the following result:

Removes any redaction reason that is applied to a view with unredacted()
Removes any redaction reason that is applied to a view with unredacted()

Caveats

At current Xcode beta (beta 6), redact a view only changes its appearance, but it can still receive user interaction. If you have a redacted button, beware that the button can still receive user action.

Redaction-aware view

We can make our view redaction-aware and respond to the redaction reason by observing the value in @Environment(\.redactionReasons).

In the following example, we modify our LandmarkView to show SF Symbols image over the Big Sur image if a redaction reason is presented.

struct LandmarkView: View {
@Environment(\.redactionReasons) private var reasons // 1

var body: some View {
VStack(alignment: .leading) {
if reasons.isEmpty { // 2
Image("bigsur")
.resizable()
.frame(width: 100, height: 100)
.clipShape(Circle())
} else {
Image("bigsur")
.resizable()
.frame(width: 100, height: 100)
.clipShape(Circle())
.overlay( // 3
Image(systemName: "photo")
.font(.system(size: 34, weight: .bold))
.foregroundColor(.white)
.unredacted())
}
Text("Big Sur \(Image(systemName: "sun.dust"))")
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.blue)
.unredacted()
Text("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy").font(.body)
Button("Bookmark", action: {
print("Bookmark")
})
}
.padding()
}
}

<1> We add @Environment(\.redactionReasons) for our view to read redaction reason and act accordingly.
<2> If the reason is not set (reasons.isEmpty), we show the image as is.
<3> If the reason is set (.redacted is applied), we show SF Symbols over our redacted image.

Redaction-aware view
Redaction-aware view

We can also tackle the button problem by disabling it if the view is being redacted.

Button("Bookmark", action: {
print("Bookmark")
}).disabled(!reasons.isEmpty)

Conclusion

Tips and tricks in this article should be enough for you to work around limitations you encounter with redaction API. Even though we only have a redaction reason right now, I think Apple will support more variation in this API's rendering effect in the future, and I'm looking forward to it.

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.
Read more article about SwiftUI or see all available topic

Enjoy the read?

If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.

Become a patron Buy me a coffee Tweet Share
Previous
Move your view around with Drag Gesture in SwiftUI

SwiftUI's UIPanGestureRecognizer equivalent.

Next
Testing delegates and protocols in XCTest

Learn how to write unit tests for delegate/protocol methods.

← Home