From 4eadd84abc1a7379c4bf6e7ea1e421e77fcf152d Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Thu, 29 Mar 2018 22:44:37 -0400 Subject: [PATCH] Don't obscure "Unlock" button with keyboard // FREEBIE --- Signal.xcodeproj/project.pbxproj | 4 ++++ Signal/src/util/OWSScreenLock.swift | 20 ++++++++++++++++ .../util/UI Categories/UIResponder+OWS.swift | 23 +++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 Signal/src/util/UI Categories/UIResponder+OWS.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 581cb5d01..c35a62037 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -323,6 +323,7 @@ 45A6DAD61EBBF85500893231 /* ReminderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45A6DAD51EBBF85500893231 /* ReminderView.swift */; }; 45AE48511E0732D6004D96C2 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */; }; 45B27B862037FFB400A539DF /* DebugUIFileBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B27B852037FFB400A539DF /* DebugUIFileBrowser.swift */; }; + 45B5360E206DD8BB00D61655 /* UIResponder+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B5360D206DD8BB00D61655 /* UIResponder+OWS.swift */; }; 45B74A742044AAB600CD42F8 /* aurora-quiet.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 45B74A5B2044AAB300CD42F8 /* aurora-quiet.aifc */; }; 45B74A752044AAB600CD42F8 /* synth-quiet.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 45B74A5C2044AAB300CD42F8 /* synth-quiet.aifc */; }; 45B74A762044AAB600CD42F8 /* keys-quiet.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 45B74A5D2044AAB400CD42F8 /* keys-quiet.aifc */; }; @@ -925,6 +926,7 @@ 45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TurnServerInfo.swift; sourceTree = ""; }; 45B201741DAECBFD00C461E0 /* Signal-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Signal-Bridging-Header.h"; sourceTree = ""; }; 45B27B852037FFB400A539DF /* DebugUIFileBrowser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugUIFileBrowser.swift; sourceTree = ""; }; + 45B5360D206DD8BB00D61655 /* UIResponder+OWS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "UIResponder+OWS.swift"; path = "util/UI Categories/UIResponder+OWS.swift"; sourceTree = ""; }; 45B74A5B2044AAB300CD42F8 /* aurora-quiet.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = "aurora-quiet.aifc"; sourceTree = ""; }; 45B74A5C2044AAB300CD42F8 /* synth-quiet.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = "synth-quiet.aifc"; sourceTree = ""; }; 45B74A5D2044AAB400CD42F8 /* keys-quiet.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = "keys-quiet.aifc"; sourceTree = ""; }; @@ -2247,6 +2249,7 @@ 45C0DC1D1E69011F00E04C47 /* UIStoryboard+OWS.swift */, EF764C331DB67CC5000D9A87 /* UIViewController+Permissions.h */, EF764C341DB67CC5000D9A87 /* UIViewController+Permissions.m */, + 45B5360D206DD8BB00D61655 /* UIResponder+OWS.swift */, ); name = "UI Categories"; path = ..; @@ -3197,6 +3200,7 @@ 34B3F8821E8DF1700035BE1A /* NewContactThreadViewController.m in Sources */, 45D308AD2049A439000189E4 /* PinEntryView.m in Sources */, 340FC8B1204DAC8D007AEB0F /* BlockListViewController.m in Sources */, + 45B5360E206DD8BB00D61655 /* UIResponder+OWS.swift in Sources */, 45F659821E1BE77000444429 /* NonCallKitCallUIAdaptee.swift in Sources */, 45AE48511E0732D6004D96C2 /* TurnServerInfo.swift in Sources */, 34B3F8771E8DF1700035BE1A /* ContactsPicker.swift in Sources */, diff --git a/Signal/src/util/OWSScreenLock.swift b/Signal/src/util/OWSScreenLock.swift index 4b19231ba..af2f2325f 100644 --- a/Signal/src/util/OWSScreenLock.swift +++ b/Signal/src/util/OWSScreenLock.swift @@ -37,6 +37,9 @@ import LocalAuthentication // Passcode-code only authentication process deactivates the app. private var ignoreUnlockUntilActive = false + // We temporarily resign any first responder while the Screen Lock is presented. + var firstResponderBeforeLockscreen: UIResponder? + // MARK - Singleton class @objc(sharedManager) @@ -169,6 +172,13 @@ import LocalAuthentication return } + // A popped keyboard breaks our layout and obscures the unlock button. + if let firstResponder = UIResponder.currentFirstResponder() { + Logger.debug("\(self.logTag) in \(#function) resigning first responder: \(firstResponder)") + firstResponder.resignFirstResponder() + self.firstResponderBeforeLockscreen = firstResponder + } + tryToVerifyLocalAuthentication(localizedReason: NSLocalizedString("SCREEN_LOCK_REASON_UNLOCK_SCREEN_LOCK", comment: "Description of how and why Signal iOS uses Touch ID/Face ID/Phone Passcode to unlock 'screen lock'."), completion: { (outcome: OWSScreenLockOutcome) in @@ -180,6 +190,16 @@ import LocalAuthentication case .unexpectedFailure(let error): unexpectedFailure(self.authenticationError(errorDescription: error)) case .success: + // It's important we restore first responder status once the user completes + // In some cases, (RegistrationLock Reminder) it just puts the keyboard back where + // the user needs it, saving them a tap. + // But in the case of an inputAccessoryView, like the ConversationViewController, + // failing to restore firstResponder could make the input toolbar disappear until + if let firstResponder = self.firstResponderBeforeLockscreen { + Logger.debug("\(self.logTag) in \(#function) regaining first responder: \(firstResponder)") + firstResponder.becomeFirstResponder() + self.firstResponderBeforeLockscreen = nil + } success() case .cancel: cancel() diff --git a/Signal/src/util/UI Categories/UIResponder+OWS.swift b/Signal/src/util/UI Categories/UIResponder+OWS.swift new file mode 100644 index 000000000..e0b817bce --- /dev/null +++ b/Signal/src/util/UI Categories/UIResponder+OWS.swift @@ -0,0 +1,23 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +// Based on https://stackoverflow.com/questions/1823317/get-the-current-first-responder-without-using-a-private-api/11768282#11768282 +extension UIResponder { + private weak static var firstResponder: UIResponder? + + public class func currentFirstResponder() -> UIResponder? { + firstResponder = nil + + // If target (`to:`) is nil, the app sends the message to the first responder, + // from whence it progresses up the responder chain until it is handled. + UIApplication.shared.sendAction(#selector(setSelfAsFirstResponder(sender:)), to: nil, from: nil, for: nil) + + return firstResponder + } + + @objc + private func setSelfAsFirstResponder(sender: AnyObject) { + UIResponder.firstResponder = self + } +}