redacted(reason: .privacy) not working as expected

I am having a lot of trouble with this. My idea was going to be to put a switch in the setting page of my app so user can choose if they want sensitive information redacted or not. Some people may want to hide their appointment on the Lock Screen, others may not.

.redacted(reason: .placeholder)

works fine when I add it to an element. but the .privacy reason docent seam to work. If I add the capability to the target 'Data Protection' every thing on the widget gets redacted, unless I mark elements with .unredacted() But to have to do that for everything is too difficult.

My thinking around how it worked was to add the modifier, redacted(reason: .privacy) to an element like a Text view or an image and if the device is locked that will be redacted. I must not have the same thinking as Apple on this process.

Replies

I use this code for the same situation as you. My iOS app lets the user choose whether to hide info on the Watch screen.

This is how I determine whether stuff should be hidden. Pass in your redactionReasons environment variable:

func getWidgetHideInfo(_ redactionReasons: RedactionReasons) -> Bool {
  // Convenience method to decide whether or not to hide sensitive info when the system wants to.
  // It is overridden by the iOS app setting: "Show information when device is locked/inactive".
	return ((redactionReasons.contains(.privacy) || redactionReasons.contains(.placeholder)) && defaultsGetSettingsWidgetPrivacy() == false)
}

defaultsGetSettingsWidgetPrivacy() gets the switch value from defaults; you may have it somewhere else. When the switch is off, it's false, and it means the user wants to hide their info, so this method is saying, when the system wants to hide stuff (.privacy) or the user wants to hide stuff, return true, meaning yes we want to hide this data.

Put this code somewhere:

extension View {
	/// Applies a modifier to a view conditionally.
	///
	/// - Parameters:
	///   - condition: The condition to determine if the content should be applied.
	///   - content: The modifier to apply to the view.
	/// - Returns: The modified view.
	@ViewBuilder func modifier<T: View>(
		if condition: @autoclosure () -> Bool,
		then content: (Self) -> T
	) -> some View {
		if condition() {
			content(self)
		} else {
			self
		}
	}

	/// Applies a modifier to a view conditionally.
	///
	/// - Parameters:
	///   - condition: The condition to determine the content to be applied.
	///   - trueContent: The modifier to apply to the view if the condition passes.
	///   - falseContent: The modifier to apply to the view if the condition fails.
	/// - Returns: The modified view.
	@ViewBuilder func modifier<TrueContent: View, FalseContent: View>(
		if condition: @autoclosure () -> Bool,
		then trueContent: (Self) -> TrueContent,
		else falseContent: (Self) -> FalseContent
	) -> some View {
		if condition() {
			trueContent(self)
		} else {
			falseContent(self)
		}
	}
}

In your views, add a line like this so you have a variable you can use that says to hide or show the data:

let hideInfo: Bool = getWidgetHideInfo(redactionReasons)

And here's the fun part (if it works). Add the following modifier to something, like a Text() or an Image(), for example:

Image(systemName: "arrow.left.square.fill")
	.font(.system(size: 32.0, weight: .regular))
	.modifier(if: hideInfo) { $0.redacted(reason: .placeholder) } else: { $0.unredacted() }

Here it is with hideInfo = false:

And here it is with hideInfo = true:

  • So will this instantly reveal the data when the device is unlocked by face the same as the Fitness (Activity) widget and Fitness Lock Screen widget?

  • Have you tried it?

    I've given you a solution that definitely works for me. It's not difficult for you to implement it in your own app and see whether it works in all your use cases. Give it a go. If it doesn't work, you've lost nothing, but perhaps it just needs a few small tweaks to work in your situation?

Add a Comment

Hi!

From our experience there might also be something else at play here. Under Settings.app -> Face ID & Passcode, there's a section called "Allow Access When Locked". You can see Lock Screen Widgets and Live Activities both are enabled by default.

That might be a reason the system is not respecting the .privacy value, because from the system's perspective, it's not in privacy redaction mode.

Can you see if the behavior changes when you disable that toggle?