With watchOS 10, we are able to add widgets into Smart Stack or use them in large complications. The Modular watch face offers one large complication. I use it to display my passwords and PIN codes using my own app Pinbook ($4.99). But there’s a problem: this complication is always visible, especially with Always On screens, and you potentially can reveal sensitive information. And I couldn’t make the complication interactive until watchOS 11.
Now in watchOS 11, Apple added interactive widgets, and I began working on updating the app. Finally, I was able to build my original vision of this product. Development took longer than expected because I also wanted to include a bunch of other features. I am using the Modular watch face with Pinbook 1.1 every day, and I find it convenient.
Now let’s dive deep into technical implementation. I am not sure my implementation is right, but it took some time to figure out and is not obvious. It is possible I am not using the most optimal strategy.
At the core, I use the NSUserDefaults flag to toggle between two states: locked and unlocked. You can only see passwords in the unlocked state. And you see a -- placeholder in the locked state.
@AppStorage("unlocked") private var unlocked = false
The @AppStorage is used in the widget View.
The only way to make the widget interactive is to use the App Intents API.
Button(intent: RevelPasswords()) {
// Widget View
}
Here’s the entire app intent.
import Foundation
import AppIntents
import WatchKit
struct RevelPasswords: AppIntent {
static var title: LocalizedStringResource = "Revel passwords"
static var description = IntentDescription("Used for widget")
func perform() async throws -> some IntentResult {
if UserDefaults.standard.bool(forKey: "unlocked") {
UserDefaults.standard.set(false, forKey: "unlocked")
} else {
UserDefaults.standard.set(true, forKey: "unlocked")
}
return .result()
}
}
Finally, my widget view.
struct ColumnView: View {
let pin: Pin
@Binding var unlocked: Bool
var body: some View {
Label {
if unlocked {
PasswordView(password: pin.code)
} else {
PasswordView(password: "--")
.privacySensitive(true)
}
} icon: {
Image(systemName: pin.symbol)
.font(.caption)
.frame(width: 8, height: 8, alignment: .center)
.widgetAccentable()
}
.onDisappear() { // This does not work.
unlocked = false
}
}
}
I use @Binding linked to @AppStorage in a parent view. Unfortunately, I have not yet found a way to toggle between the 2 states programmatically. For example, when the Apple Watch screen is not active, I could automatically hide passwords. I have tried to use .onDisappear(), and it didn’t work. I am sure there’s a way to do it, but I just didn’t have enough time to figure it out yet.