Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ios): configurable keyboard height #12571

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
246efb4
add resizeKeyboard logging
sgschantz Oct 23, 2024
751c853
correct resizeKeyboard logging
sgschantz Oct 23, 2024
0fbf123
fix(ios): add Adjust Keyboard Height control to Keyman Settings
sgschantz Oct 24, 2024
7b69bed
feat(ios): add KeyboardHeightViewController
sgschantz Oct 25, 2024
8695112
fix(ios): add subviews for keyboard height adjustment
sgschantz Nov 1, 2024
048b317
fix(ios): add debug logs
sgschantz Nov 1, 2024
6decb5d
fix(ios): more image layout debugging and experimenting
sgschantz Nov 1, 2024
9ff6d70
fix(ios): fix keyboard height layout for portrait
sgschantz Nov 4, 2024
9035414
fix(ios): fully functional UI for keyboard height change
sgschantz Nov 14, 2024
b620e17
fix(ios): save keyboard height in UserDefaults
sgschantz Nov 15, 2024
a8949ef
fix(ios): check for existence of UserDefault setting before read
sgschantz Nov 15, 2024
8caf106
fix(ios): write new height values to UserDefaults
sgschantz Nov 15, 2024
fe6c818
fix(ios): return persisted height from constraintTargetHeight
sgschantz Nov 15, 2024
e989c57
fix(ios): try alternate image loading method
sgschantz Nov 21, 2024
b9aae00
fix(ios): use engine bundle to find image
sgschantz Nov 22, 2024
607c5cc
fix(ios): added new blank keyboard assets to keyman engine
sgschantz Nov 26, 2024
fbfb7f3
fix(ios): use engine bundle -- not Keyman.bundle in engine
sgschantz Nov 26, 2024
625eed5
fix(ios): add strings to bundle for localization
sgschantz Nov 26, 2024
c3c8e0b
fix(ios): fix rotation and resize animations
sgschantz Nov 27, 2024
221ba7b
fix(ios): attempt to reload in-app keyboard on keyboard height change
sgschantz Nov 27, 2024
3e54255
fix(ios): check view changes during device rotation
sgschantz Dec 2, 2024
c47fab0
fix(ios): use UIScreen.main.bounds to determine orientation
sgschantz Dec 2, 2024
7c0657a
fix(ios): update constraints upon keyboard height change
sgschantz Dec 5, 2024
2d3cde7
fix(ios): allow asynchronous layout update
sgschantz Dec 5, 2024
f2ff959
fix(ios): add logging, present keyboard height view modally
sgschantz Dec 6, 2024
b4fb78a
fix(ios): add more logging to determine first call for keyboard size
sgschantz Dec 6, 2024
51d568a
fix(ios): eliminate circular logging reference in initKeyboardSize()
sgschantz Dec 7, 2024
026edfe
fix(ios): reworked initialization of keyboardSize
sgschantz Dec 7, 2024
c9d4c5c
fix(ios): clean up and reduce log statements
sgschantz Dec 9, 2024
b1d23b2
fix(ios): writeKeyboardHeightIfDoesNotExist logic error
sgschantz Dec 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
1645D5952036C6FF0076C51B /* KeymanPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1645D5942036C6FF0076C51B /* KeymanPackage.swift */; };
1645D5972036C9F80076C51B /* KMPKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1645D5962036C9F80076C51B /* KMPKeyboard.swift */; };
165EB3A12098993900040A69 /* KeyboardError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 165EB3A02098993900040A69 /* KeyboardError.swift */; };
29084CAB2CD48B5D004070E7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 29084CAA2CD48B5D004070E7 /* Images.xcassets */; };
296EF2C72AFA26C700E3E384 /* ZIPFoundation.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 296EF2C62AFA26C700E3E384 /* ZIPFoundation.xcframework */; };
29B30C232B564F9900C342A4 /* KeymanEngineLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B30C222B564F9900C342A4 /* KeymanEngineLogger.swift */; };
29E202BD2CCB7541008B4740 /* KeyboardHeightViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E202BC2CCB7541008B4740 /* KeyboardHeightViewController.swift */; };
377D10DE26846B8900467431 /* SpacebarTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377D10DD26846B8900467431 /* SpacebarTextViewController.swift */; };
6CD5DFAA150F6DC8007A5DDE /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 6CD5DFA8150F6DC8007A5DDE /* icon.png */; };
6CD5DFAB150F6DC8007A5DDE /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = 6CD5DFA9150F6DC8007A5DDE /* [email protected] */; };
Expand Down Expand Up @@ -298,6 +300,7 @@
1645D5942036C6FF0076C51B /* KeymanPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeymanPackage.swift; sourceTree = "<group>"; };
1645D5962036C9F80076C51B /* KMPKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMPKeyboard.swift; sourceTree = "<group>"; };
165EB3A02098993900040A69 /* KeyboardError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardError.swift; sourceTree = "<group>"; };
29084CAA2CD48B5D004070E7 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
293EA3DB2705955300545EED /* ha */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ha; path = ha.lproj/ResourceInfoView.strings; sourceTree = "<group>"; };
293EA3DC2705964200545EED /* ha */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ha; path = ha.lproj/Localizable.strings; sourceTree = "<group>"; };
293EA3DD270596B700545EED /* ha */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ha; path = ha.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
Expand Down Expand Up @@ -333,6 +336,7 @@
29C1E17128001F7600759EDE /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/ResourceInfoView.strings"; sourceTree = "<group>"; };
29C1E17228001F8800759EDE /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = "<group>"; };
29C1E17328001FA200759EDE /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pt-PT"; path = "pt-PT.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
29E202BC2CCB7541008B4740 /* KeyboardHeightViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardHeightViewController.swift; sourceTree = "<group>"; };
377D10DD26846B8900467431 /* SpacebarTextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpacebarTextViewController.swift; sourceTree = "<group>"; };
6C0A140E151EA930007FA4AD /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
6C0A140F151EA930007FA4AD /* Keyman.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; lineEnding = 0; path = Keyman.xcconfig; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.xcconfig; };
Expand Down Expand Up @@ -705,6 +709,7 @@
9A60763C22892485003BCFBA /* Settings.storyboard */,
9A60764322893A4E003BCFBA /* SettingsViewController.swift */,
377D10DD26846B8900467431 /* SpacebarTextViewController.swift */,
29E202BC2CCB7541008B4740 /* KeyboardHeightViewController.swift */,
);
name = Settings;
sourceTree = "<group>";
Expand Down Expand Up @@ -760,6 +765,7 @@
CEA1486F2407808F00C6ECD2 /* Localizable.strings */,
CE96E42D24D1229A005B8E5A /* Localizable.stringsdict */,
CE7A26D023CEE5790005955C /* Keyboard Colors.xcassets */,
29084CAA2CD48B5D004070E7 /* Images.xcassets */,
C06D372E1F81F4E100F61AE0 /* Info.plist */,
F273AB9615641D9300A47CEE /* Classes */,
);
Expand Down Expand Up @@ -1260,6 +1266,7 @@
buildActionMask = 2147483647;
files = (
C06D37601F82095200F61AE0 /* Keyman.bundle in Resources */,
29084CAB2CD48B5D004070E7 /* Images.xcassets in Resources */,
CEA1486C2407808F00C6ECD2 /* Localizable.strings in Resources */,
9ADC459F22E1895D004C78C6 /* LanguageLMDetailViewController.xib in Resources */,
CEA14870240780E100C6ECD2 /* ResourceInfoView.xib in Resources */,
Expand Down Expand Up @@ -1447,6 +1454,7 @@
CE67D961228A6F190029F2B5 /* KeyboardCommandStructs.swift in Sources */,
CE87751E24C68DA500B1475A /* KeyboardSearchViewController.swift in Sources */,
CE8B0BBF248764ED0045EB2E /* KMPResource.swift in Sources */,
29E202BD2CCB7541008B4740 /* KeyboardHeightViewController.swift in Sources */,
9AD4F53C229F85AC007992D3 /* LanguageSettingsViewController.swift in Sources */,
CE969BE8251AD8B500376D6A /* PackageWebViewController.swift in Sources */,
CE7A26DB23CEEF640005955C /* Colors.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions ios/engine/KMEI/KeymanEngine/Classes/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public enum Key {
static let synchronizeSWLexicalModel = "KeymanSynchronizeSWLexicalModel"

static let migrationLevel = "KeymanEngineMigrationLevel"
static let portraitKeyboardHeight = "PortraitKeyboardHeight"
static let landscapeKeyboardHeight = "LandscapeKeyboardHeight"

// JSON keys for language REST calls
static let options = "options"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,27 @@ public extension UserDefaults {
set(prefs, forKey: Key.userCorrectSettings)
}
}


var portraitKeyboardHeight: Double {
get {
return double(forKey: Key.portraitKeyboardHeight)
}

set(height) {
set(height, forKey: Key.portraitKeyboardHeight)
}
}

var landscapeKeyboardHeight: Double {
get {
return double(forKey: Key.landscapeKeyboardHeight)
}

set(height) {
set(height, forKey: Key.landscapeKeyboardHeight)
}
}

func predictSettingForLanguage(languageID: String) -> Bool {
if let dict = predictionEnablements {
return dict[languageID] ?? true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ private class CustomInputView: UIInputView, UIInputViewAudioFeedback {
}

func setConstraints() {
os_log("CustomInputView setConstraints", log: KeymanEngineLogger.ui, type: .info)
let innerView = keymanWeb.view!
let guide = self.safeAreaLayoutGuide

Expand All @@ -88,18 +89,48 @@ private class CustomInputView: UIInputView, UIInputViewAudioFeedback {
kbdWidthConstraint.priority = .defaultHigh
kbdWidthConstraint.isActive = true

let bannerHeight = InputViewController.topBarHeight
self.buildKeyboardHeightConstraints(bannerHeight: InputViewController.topBarHeight)
}

/**
* Due to new custom keyboard height as chosen by the user.
* The value for the new keyboard height originates from KeyboardHeightViewController.
*/
func keyboardHeightChanged() {
os_log("CustomInputView keyboardHeightChanged", log: KeymanEngineLogger.ui, type: .info)

// deactivate constraints for both orientations (though one should already be inactive)
landscapeConstraint?.isActive = false
portraitConstraint?.isActive = false

// rebuild both portrait and landscape constraints
self.buildKeyboardHeightConstraints(bannerHeight: InputViewController.topBarHeight)

// activate constraints for the current orientation
if InputViewController.isPortrait {
portraitConstraint?.isActive = true
} else {
landscapeConstraint?.isActive = true
}

self.setNeedsLayout()
}

private func buildKeyboardHeightConstraints(bannerHeight: CGFloat) {
os_log("CustomInputView buildKeyboardHeightConstraints", log: KeymanEngineLogger.ui, type: .info)
let innerView = keymanWeb.view!

// Cannot be met by the in-app keyboard, but helps to 'force' height for the system keyboard.
let portraitHeight = innerView.heightAnchor.constraint(equalToConstant: bannerHeight + keymanWeb.constraintTargetHeight(isPortrait: true))
portraitHeight.identifier = "Height constraint for portrait mode"
portraitHeight.priority = .defaultHigh
let landscapeHeight = innerView.heightAnchor.constraint(equalToConstant: bannerHeight + keymanWeb.constraintTargetHeight(isPortrait: false))
landscapeHeight.identifier = "Height constraint for landscape mode"
landscapeHeight.priority = .defaultHigh

portraitConstraint = portraitHeight
landscapeConstraint = landscapeHeight
let portraitHeightConstraint = innerView.heightAnchor.constraint(equalToConstant: bannerHeight + keymanWeb.readKeyboardHeight(isPortrait: true)!)
portraitHeightConstraint.identifier = "Height constraint for portrait mode"
portraitHeightConstraint.priority = .defaultHigh

let landscapeHeightConstraint = innerView.heightAnchor.constraint(equalToConstant: bannerHeight + keymanWeb.readKeyboardHeight(isPortrait: false)!)
landscapeHeightConstraint.identifier = "Height constraint for landscape mode"
landscapeHeightConstraint.priority = .defaultHigh

portraitConstraint = portraitHeightConstraint
landscapeConstraint = landscapeHeightConstraint
// .isActive will be set according to the current portrait/landscape perspective.
}

Expand Down Expand Up @@ -153,7 +184,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
}

var expandedHeight: CGFloat {
return keymanWeb.keyboardHeight + InputViewController.topBarHeight
return keymanWeb.keyboardSize.height + InputViewController.topBarHeight
}

public convenience init() {
Expand Down Expand Up @@ -504,21 +535,32 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
}

public var kmwHeight: CGFloat {
return keymanWeb.keyboardHeight
return keymanWeb.keyboardSize.height
}

func clearModel() {
keymanWeb.activeModel = false
}

private func setInnerConstraints() {
let iv = self.inputView as! CustomInputView
iv.setConstraints()
let customInputView = self.inputView as! CustomInputView
customInputView.setConstraints()

self.updateViewConstraints()
fixLayout()
}

/**
* Due to new custom keyboard height as chosen by the user.
* The value for the new keyboard height originates from KeyboardHeightViewController.
*/
func keyboardHeightChanged() {
os_log("InputViewController keyboardHeightChanged", log: KeymanEngineLogger.ui, type: .debug)
if let customInputView = self.inputView as? CustomInputView {
customInputView.keyboardHeightChanged()
}
}

func fixLayout() {
view.setNeedsLayout()
view.layoutIfNeeded()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ class KeyboardScaleMap {
screenSize: CGSize = UIScreen.main.bounds.size,
asPhone: Bool? = nil) -> KeyboardSize? {
if let scaling = shared.scalings[KeyboardScaleMap.hashKey(for: device)] {
let kbHeight = (forPortrait ? scaling.portrait : scaling.landscape).keyboardHeight
os_log("KeyboardScaleMap getDeviceDefaultKeyboardScale keyboard height: %f", log:KeymanEngineLogger.ui, type: .debug, kbHeight)
return forPortrait ? scaling.portrait : scaling.landscape
}

Expand All @@ -240,6 +242,9 @@ class KeyboardScaleMap {
// keyboard dimensions. It's not a perfect rule, but should suffice for a stop-gap solution.
let scaling = shared.scalings[KeyboardScaleMap.hashKey(for: mappedDevice)]!

let kbHeight = (forPortrait ? scaling.portrait : scaling.landscape).keyboardHeight
os_log("KeyboardScaleMap getDeviceDefaultKeyboardScale keyboard height for missing device: %f", log:KeymanEngineLogger.ui, type: .debug, kbHeight)

return forPortrait ? scaling.portrait : scaling.landscape
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ class KeymanWebViewController: UIViewController {
// after it has been replaced by KMW's OSK resizing operation.)

keyboardSize = view.bounds.size
os_log("KeymanWebViewController viewWillLayoutSubviews to keyboardSize %{public}s", log:KeymanEngineLogger.ui, type: .debug, NSCoder.string(for:keyboardSize))
}

open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
Expand Down Expand Up @@ -695,36 +696,100 @@ extension KeymanWebViewController: KeymanWebDelegate {
// MARK: - Manage views
extension KeymanWebViewController {
// MARK: - Sizing
public var keyboardHeight: CGFloat {
return keyboardSize.height
}

@objc func menuKeyHeld(_ keymanWeb: KeymanWebViewController) {
self.delegate?.menuKeyHeld(self)
}

func constraintTargetHeight(isPortrait: Bool) -> CGFloat {
return KeyboardScaleMap.getDeviceDefaultKeyboardScale(forPortrait: isPortrait)?.keyboardHeight ?? 216 // default for ancient devices
}
func determineDefaultKeyboardHeight(isPortrait: Bool) -> CGFloat {
os_log("determineDefaultKeyboardHeight", log:KeymanEngineLogger.ui, type: .info)

var keyboardWidth: CGFloat {
return keyboardSize.width
let keyboardHeight = KeyboardScaleMap.getDeviceDefaultKeyboardScale(forPortrait: isPortrait)?.keyboardHeight ?? 216 // default for ancient devices

return keyboardHeight;
}

func initKeyboardSize() {
var width: CGFloat
var height: CGFloat
let width: CGFloat
width = UIScreen.main.bounds.width

if Util.isSystemKeyboard {
height = constraintTargetHeight(isPortrait: InputViewController.isPortrait)
var height: CGFloat

// get orientation differently if system or in-app keyboard
let portrait = Util.isSystemKeyboard ? InputViewController.isPortrait : UIDevice.current.orientation.isPortrait

/**
* If keyboard height is saved in UserDefaults, then use it
*/
if let savedHeight = self.readKeyboardHeight(isPortrait: portrait) {
height = savedHeight
} else {
height = constraintTargetHeight(isPortrait: UIDevice.current.orientation.isPortrait)
/**
* Otherwise, get default keyboard height for this orientation and write to UserDefaults
*/
height = self.determineDefaultKeyboardHeight(isPortrait: portrait)
self.writeKeyboardHeightIfDoesNotExist(isPortrait: portrait, height: height)

/**
* If we need to write out the keyboard height for one orientation, then we
* expect that the other must be written also.
* Write it out now, but only if a value for keyboard height does not already exist.
*/
height = self.determineDefaultKeyboardHeight(isPortrait: !portrait)
self.writeKeyboardHeightIfDoesNotExist(isPortrait: !portrait, height: height)
}

/**
* no need to check for Util.isSystemKeyboard because this is shared storage
* the UserDefaults are readable and writeable by both system and in-app
*/

keyboardSize = CGSize(width: width, height: height)
self.keyboardSize = CGSize(width: width, height: height)
os_log("KeymanWebViewController initKeyboardSize %{public}s", log:KeymanEngineLogger.ui, type: .default, NSCoder.string(for:keyboardSize))
}

/**
* reads and returns keyboard height if it is found in UserDefaults, otherwise returns nil
*/
func readKeyboardHeight(isPortrait: Bool) -> CGFloat? {
var height: CGFloat? = nil

if (isPortrait) {
if (Storage.active.userDefaults.object(forKey: Key.portraitKeyboardHeight) != nil) {
height = Storage.active.userDefaults.portraitKeyboardHeight
}
} else { // landscape
if (Storage.active.userDefaults.object(forKey: Key.landscapeKeyboardHeight) != nil) {
height = Storage.active.userDefaults.landscapeKeyboardHeight
}
}

let message = "readKeyboardHeight, for isPortrait \(isPortrait) value \(String(describing: height))"
os_log("%{public}s", log:KeymanEngineLogger.ui, type: .info, message)

return height;
}

/**
* Write out the keyboard height to the UserDefaults but only if it does not exist there yet.
* If it exists, then we assume it was configured by the user and do not want to
* overwrite that value with a default value derived for this device.
*/
func writeKeyboardHeightIfDoesNotExist(isPortrait: Bool, height: CGFloat) {
let writeMessage = "writeKeyboardHeightIfDoesNotExist, isPortrait: \(isPortrait) height: \(height)"
os_log("%{public}s", log:KeymanEngineLogger.ui, type: .info, writeMessage)
if (isPortrait) {
if (Storage.active.userDefaults.object(forKey: Key.portraitKeyboardHeight) == nil) {
Storage.active.userDefaults.portraitKeyboardHeight = height
os_log("portrait keyboardHeight default value written", log:KeymanEngineLogger.ui, type: .info, writeMessage)
}
} else {
if (Storage.active.userDefaults.object(forKey: Key.landscapeKeyboardHeight) == nil) {
Storage.active.userDefaults.landscapeKeyboardHeight = height
os_log("landscape keyboardHeight default value written", log:KeymanEngineLogger.ui, type: .info, writeMessage)
}
}
}

var keyboardSize: CGSize {
get {
if kbSize.equalTo(CGSize.zero) {
Expand All @@ -750,15 +815,6 @@ extension KeymanWebViewController {
}
}

@objc func resizeDelay() {
// + 1000 to work around iOS bug with resizing on landscape orientation. Technically we only
// need this for landscape but it doesn't hurt to do it with both. 1000 is a big number that
// should hopefully work on all devices.
let kbWidth = keyboardWidth
let kbHeight = keyboardHeight
view.frame = CGRect(x: 0.0, y: 0.0, width: kbWidth, height: kbHeight + 1000)
}

// Keyman interaction
func resizeKeyboard() {
fixLayout()
Expand All @@ -768,6 +824,7 @@ extension KeymanWebViewController {
// the first time.
setOskWidth(Int(kbSize.width))
setOskHeight(Int(kbSize.height))
os_log("KeymanWebViewController resizeKeyboard to kbSize %{public}s", log:KeymanEngineLogger.ui, type: .debug, NSCoder.string(for:kbSize))
}

func resetKeyboardState() {
Expand Down
Loading
Loading