Skip to content

Instantly share code, notes, and snippets.

@sroebert
Created September 8, 2023 22:08
Show Gist options
  • Save sroebert/77554248276ac6fe28a83c553c4f48fa to your computer and use it in GitHub Desktop.
Save sroebert/77554248276ac6fe28a83c553c4f48fa to your computer and use it in GitHub Desktop.
SwiftUI LockableView
import SwiftUI
import UIKit
struct ContentView: View {
@State private var isLocked = false
@State private var isSheetVisible = false
var body: some View {
LockableView(isLocked: isLocked) {
Color.white
.ignoresSafeArea()
.overlay {
VStack {
Text("This is the app")
Button("Click here to present sheet") {
isSheetVisible.toggle()
}
Button("Click here to lock") {
isLocked.toggle()
}
.foregroundStyle(Color.red)
}
}
.sheet(isPresented: $isSheetVisible) {
Color.gray
.ignoresSafeArea()
.overlay {
VStack {
Text("This is a sheet")
Button("Click here to dismiss sheet") {
isSheetVisible.toggle()
}
Button("Click here to lock") {
isLocked.toggle()
}
.foregroundStyle(Color.red)
}
}
}
} lockedOverlay: {
Color.black
.ignoresSafeArea()
.overlay {
VStack {
Text("This is the lock screen")
.foregroundStyle(Color.white)
Button("Click here to unlock") {
isLocked.toggle()
}
.foregroundStyle(Color.green)
}
}
}
.ignoresSafeArea()
}
}
struct LockableView<Content: View, LockedOverlay: View>: UIViewControllerRepresentable {
var isLocked: Bool
@ViewBuilder var content: Content
@ViewBuilder var lockedOverlay: LockedOverlay
func makeUIViewController(context: Context) -> LockableViewController<Content, LockedOverlay> {
LockableViewController(content: content, lockedOverlay: lockedOverlay, isLocked: isLocked)
}
func updateUIViewController(_ uiViewController: LockableViewController<Content, LockedOverlay>, context: Context) {
uiViewController.update(content: content, lockedOverlay: lockedOverlay, isLocked: isLocked)
}
}
class LockableViewController<Content: View, LockedOverlay: View>: UIHostingController<Content> {
private var lockedWindow: UIWindow?
private let lockedHostingController: UIHostingController<LockedOverlay>
private var isLocked: Bool
init(
content: Content,
lockedOverlay: LockedOverlay,
isLocked: Bool
) {
lockedHostingController = UIHostingController(rootView: lockedOverlay)
self.isLocked = isLocked
super.init(rootView: content)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if isLocked {
showLock()
}
}
private func showLock() {
guard lockedWindow == nil, let windowScene = view.window?.windowScene else {
return
}
let lockedWindow = UIWindow(windowScene: windowScene)
lockedWindow.rootViewController = lockedHostingController
lockedWindow.alpha = 0
lockedWindow.makeKeyAndVisible()
self.lockedWindow = lockedWindow
UIView.animate(withDuration: 0.3) {
lockedWindow.alpha = 1
}
}
private func hideLock() {
guard let window = lockedWindow else {
return
}
self.lockedWindow = nil
UIView.animate(withDuration: 0.3) {
window.alpha = 0
} completion: { _ in
// Keep a reference to the window during the animation,
// to make sure it does not immediately disappear
_ = window
}
}
func update(
content: Content,
lockedOverlay: LockedOverlay,
isLocked: Bool
) {
rootView = content
lockedHostingController.rootView = lockedOverlay
if isLocked != self.isLocked {
self.isLocked = isLocked
if isLocked {
showLock()
} else {
hideLock()
}
}
}
}
@ondrejkondek
Copy link

Thank you very much - this is really nice work from you.

@Mika5652
Copy link

Mika5652 commented Feb 19, 2024

Thank you, it helped me a lot!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment