You are currently viewing How to Create Custom Redacted Effects on Widgets?

How to Create Custom Redacted Effects on Widgets?

In my previous article, we talked about how we can protect our users’ privacy by redacting widget data. It’s a simple process, but unfortunately, there aren’t many options available when it comes to the redacted effect.

In this article, I’ll show you how you can create your own unique redacted effect. This will enhance the visual appeal of your widget when data is redacted and make it look more professional.

Moreover, there’s something you need to be aware of. I noticed a strange behavior in iOS 16 when applying custom redacted effects to a widget. Therefore, we’ll be discussing that as well and showing you how to work around it.

So, without further ado, let’s get started!


The Straightforward Way

Let’s use the Bitcoin wallet widget we created in my previous article and apply a blur effect on the balance value.

Blur redaction effect on widgets using Swift
Blur redacted effect on a widget

We can achieve this by using the redactionReasons environment key in SwiftUI to determine the current redaction state of the view.

Once we have the current redaction state, we can apply the blur effect accordingly. Here’s how:

@Environment(\.redactionReasons) var reasons

var body: some View {
    
    VStack(alignment: .leading) {
        
        Text("Bitcoin Balance")
            .font(.title2)
            .fontWeight(.bold)
            .foregroundColor(.orange)
        
        if reasons.isEmpty {
            // Show balance
            Text("0.25₿")
                .font(.headline)
                .fontWeight(.semibold)
                .foregroundColor(.gray)
        } else {
            // Hide balance
            Text("0.25₿")
                .font(.headline)
                .fontWeight(.semibold)
                .foregroundColor(.gray)
                .blur(radius: 4)
        }
    }
}

While this approach is simple and effective, it unfortunately only works on iOS 15 and lower.

What do I mean? Let me show you.


The Strange Behavior on iOS 16

In iOS 16, Apple has introduced a new privacy setting that affects the accessibility of the Lock Screen widgets when the device is locked, causing the aforementioned approach unusable.

New privacy settings introduced in iOS 16
New privacy settings introduced in iOS 16

As mentioned in my previous article, turning on the setting causes the .privacySensitive() modifier to stop working, and the same goes for the custom redacted effect. On the other hand, turning off the setting causes the entire widget to be redacted with the default redact style.

Widget fully redacted with the default redact style in iOS
Widget fully redacted with the default redact style

Now, here’s where it gets weird. Adding a privacy-sensitive UI element to the widget’s body suddenly causes everything to work as expected.

How to Create Custom Redacted Effects on Widgets using Swift in Xcode
Unexpected outcome after adding a privacy-sensitive UI element to the widget’s body

This feels really weird to me and seems like other fellow developers on Twitter and LinkedIn also feel the same too.

As of this writing (iOS 16.4), this odd behavior still exists. Hopefully, Apple will provide an official explanation in the near future.


Working Around the Strange Behavior

Based on what we just observed, the easiest workaround to this behavior is to add a privacy-sensitive Text view with zero height in our widget.

@Environment(\.redactionReasons) var reasons

var body: some View {
        
    ZStack {
        // Zero height Text view
        Text("---")
            .privacySensitive()
            .frame(maxWidth: .infinity, maxHeight: 0.0)
            .clipped()
        
        // Widget UI here
        // ...
        // ...
    }
}

With the above workaround, we can maintain the original UI layout while achieving the desired custom redacted effect.

To make our code more readable and easier to use, we can create a View extension.

extension View {
    
    func customRedactActivate() -> some View {
        ZStack {
            Text("---")
                .privacySensitive()
                .frame(maxWidth: .infinity, maxHeight: 0.0)
                .clipped()
            self
        }
    }
}

With the View extension in place, we can simply call customRedactActivate() on any widget view like so:

VStack(alignment: .leading) {
    
    Text("Bitcoin Balance")
        .font(.title2)
        .fontWeight(.bold)
        .foregroundColor(.orange)
    
    if reasons.isEmpty {
        // Show balance
        Text("0.25₿")
            .font(.headline)
            .fontWeight(.semibold)
            .foregroundColor(.gray)
    } else {
        // Hide balance
        Text("0.25₿")
            .font(.headline)
            .fontWeight(.semibold)
            .foregroundColor(.gray)
            .blur(radius: 4)
    }
}
.customRedactActivate()

Doing so not only increases the reusability of our code but also makes our code more concise and easier to understand.


Further Readings


What are your thoughts on the strange behavior? Do you have any suggestions for a better workaround? Don’t hesitate to reach out to me on Twitter or LinkedIn to share your ideas.

Also, make sure to follow me on Twitter or LinkedIn and subscribe to my newsletter to stay up-to-date with my latest iOS development-related articles.

Thanks for reading! 👨🏻‍💻


👋🏻 Hey!

While you’re still here, why not check out some of my favorite Mac tools on Setapp? They will definitely help improve your day-to-day productivity. Additionally, doing so will also help support my work.

  • Bartender: Superpower your menu bar and take full control over your menu bar items.
  • CleanShot X: The best screen capture app I’ve ever used.
  • PixelSnap: Measure on-screen elements with ease and precision.
  • iStat Menus: Track CPU, GPU, sensors, and more, all in one convenient tool.