最完整的iOS虚拟键盘开发指南:从模仿到定制Tasty Imitation Keyboard

最完整的iOS虚拟键盘开发指南:从模仿到定制Tasty Imitation Keyboard

【免费下载链接】tasty-imitation-keyboard A custom keyboard for iOS8 that serves as a tasty imitation of the default Apple keyboard. Built using Swift and the latest Apple technologies! 【免费下载链接】tasty-imitation-keyboard 项目地址: https://gitcode.com/gh_mirrors/ta/tasty-imitation-keyboard

读完你能得到

  • 掌握iOS自定义键盘(Custom Keyboard Extension)的完整开发流程
  • 理解Tasty Imitation Keyboard的架构设计与核心实现
  • 学习Swift语言在键盘开发中的最佳实践
  • 解决键盘布局适配、按键交互等关键技术难题
  • 实现自动大写、双击空格输入句号等高级功能

为什么需要自定义键盘?

在iOS开发中,系统键盘虽然功能完善,但在特定场景下仍无法满足个性化需求。无论是企业级应用需要集成安全输入、教育类App需要特殊符号输入,还是创意应用需要独特的交互方式,自定义键盘都成为不可或缺的解决方案。Tasty Imitation Keyboard作为一个开源的仿原生键盘项目,为我们提供了绝佳的学习范例。

项目架构概览

Tasty Imitation Keyboard采用MVC架构模式,将数据模型、界面展示和业务逻辑清晰分离:

mermaid

核心组件说明

组件作用关键类
控制器处理用户交互和生命周期KeyboardViewController
布局管理键盘按键位置和尺寸KeyboardLayout
模型定义键盘结构和按键属性KeyboardModel, Key
视图绘制按键和处理触摸KeyboardKey, ForwardingView
设置管理用户偏好设置DefaultSettings

环境搭建与项目配置

开发环境要求

  • Xcode 10.0+
  • iOS 8.0+ SDK
  • Swift 4.0+

项目克隆与构建

git clone https://link.gitcode.com/i/a8455ba8c7dfccaf6974b9ebf062b879.git
cd tasty-imitation-keyboard
open TastyImitationKeyboard.xcodeproj

关键配置步骤

  1. 启用键盘扩展:在Xcode项目设置中,确保Keyboard目标已正确配置为扩展
  2. 设置Info.plist:添加必要的权限和支持的语言
  3. 配置主应用:设置HostingApp作为键盘的容器应用

核心功能实现详解

1. 键盘布局系统

Tasty Imitation Keyboard的布局系统是其最复杂也最核心的部分,通过动态计算确保在不同设备上的完美适配:

// KeyboardLayout.swift
func generateKeyFrames(_ model: Keyboard, bounds: CGRect, page pageToLayout: Int) -> [Key:CGRect]? {
    if bounds.height == 0 || bounds.width == 0 {
        return nil
    }
    
    let isPortrait = bounds.width < bounds.height
    let keyboardWidth = layoutConstants.keyboardShrunkSize(bounds.width)
    let sideEdges = isPortrait ? layoutConstants.sideEdgesPortrait(bounds.width) : layoutConstants.sideEdgesLandscape
    let topEdge = isPortrait ? layoutConstants.topEdgePortrait(bounds.width) : layoutConstants.topEdgeLandscape
    
    // 计算可用区域
    let availableWidth = keyboardWidth - 2 * sideEdges
    let availableHeight = bounds.height - topEdge
    
    // 计算行高和行间距
    let rowCount = model.pages[pageToLayout].rows.count
    let rowGap = isPortrait ? layoutConstants.rowGapPortrait(bounds.width) : layoutConstants.rowGapLandscape
    let totalRowGap = rowGap * CGFloat(rowCount - 1)
    let rowHeight = (availableHeight - totalRowGap) / CGFloat(rowCount)
    
    // 为每个按键生成Frame
    var keyFrames = [Key:CGRect]()
    var currentY = topEdge
    
    for (rowIndex, row) in model.pages[pageToLayout].rows.enumerated() {
        // 计算每行按键宽度
        let keyCount = row.count
        let keyGap = isPortrait ? layoutConstants.keyGapPortrait(bounds.width, rowCharacterCount: keyCount) : layoutConstants.keyGapLandscape(bounds.width, rowCharacterCount: keyCount)
        let totalKeyGap = keyGap * CGFloat(keyCount - 1)
        let keyWidth = (availableWidth - totalKeyGap) / CGFloat(keyCount)
        
        var currentX = sideEdges
        
        for key in row {
            let keyFrame = CGRect(x: currentX, y: currentY, width: keyWidth, height: rowHeight)
            keyFrames[key] = keyFrame
            currentX += keyWidth + keyGap
        }
        
        currentY += rowHeight + rowGap
    }
    
    return keyFrames
}

2. 按键交互处理

键盘的核心交互逻辑在KeyboardViewController中实现,包括按键按下、抬起、长按等事件:

// KeyboardViewController.swift
func keyPressedHelper(_ sender: KeyboardKey) {
    if let model = self.layout?.keyForView(sender) {
        self.keyPressed(model)
        
        // 空格键或回车键后自动切换回字母键盘
        if model.type == Key.KeyType.space || model.type == Key.KeyType.return {
            self.currentMode = 0
        }
        // 处理单引号后自动切换回字母键盘
        else if model.lowercaseOutput == "'" {
            self.currentMode = 0
        }
        
        // 双击空格输入句号功能
        self.handleAutoPeriod(model)
    }
    
    // 更新大小写状态
    self.updateCapsIfNeeded()
}

func handleAutoPeriod(_ key: Key) {
    if !UserDefaults.standard.bool(forKey: kPeriodShortcut) {
        return
    }
    
    if self.autoPeriodState == .firstSpace {
        if key.type != Key.KeyType.space {
            self.autoPeriodState = .noSpace
            return
        }
        
        // 检查前序文本是否符合条件
        let charactersAreInCorrectState = { () -> Bool in
            guard let previousContext = self.textDocumentProxy.documentContextBeforeInput else {
                return false
            }
            
            if previousContext.count < 3 {
                return false
            }
            
            var index = previousContext.endIndex
            index = previousContext.index(before: index)
            if previousContext[index] != " " { return false }
            
            index = previousContext.index(before: index)
            if previousContext[index] != " " { return false }
            
            index = previousContext.index(before: index)
            let char = previousContext[index]
            return !self.characterIsWhitespace(char) && !self.characterIsPunctuation(char)
        }()
        
        if charactersAreInCorrectState {
            // 删除两个空格并输入句号和一个空格
            self.textDocumentProxy.deleteBackward()
            self.textDocumentProxy.deleteBackward()
            self.textDocumentProxy.insertText(". ")
        }
        
        self.autoPeriodState = .noSpace
    } else {
        if key.type == Key.KeyType.space {
            self.autoPeriodState = .firstSpace
        }
    }
}

3. 自动大写功能实现

自动大写是提升用户体验的重要功能,实现逻辑如下:

// KeyboardViewController.swift
func shouldAutoCapitalize() -> Bool {
    if !UserDefaults.standard.bool(forKey: kAutoCapitalization) {
        return false
    }
    
    let proxy = textDocumentProxy
    let autocapitalizationType = proxy.autocapitalizationType
    
    switch autocapitalizationType {
    case .words, .sentences, .allCharacters:
        break
    default:
        return false
    }
    
    // 获取输入上下文
    let previousContext = proxy.documentContextBeforeInput ?? ""
    
    // 句子开头自动大写
    if autocapitalizationType == .sentences {
        let terminators = [".", "!", "?"]
        let hasTerminator = terminators.contains { previousContext.hasSuffix($0) }
        
        if hasTerminator || previousContext.isEmpty {
            return true
        }
    }
    
    // 单词开头自动大写
    if autocapitalizationType == .words {
        let whitespaceCharacters = CharacterSet.whitespacesAndNewlines
        let lastCharacter = previousContext.unicodeScalars.last
        
        if previousContext.isEmpty || (lastCharacter != nil && whitespaceCharacters.contains(lastCharacter!)) {
            return true
        }
    }
    
    // 全部大写
    if autocapitalizationType == .allCharacters {
        return true
    }
    
    return false
}

4. 多页面切换机制

Tasty Imitation Keyboard实现了字母、数字和符号三个页面的切换功能:

// KeyboardViewController.swift
func setMode(_ mode: Int) {
    self.forwardingView.resetTrackedViews()
    self.shiftStartingState = nil
    self.shiftWasMultitapped = false
    
    let uppercase = self.shiftState.uppercase()
    let characterUppercase = (UserDefaults.standard.bool(forKey: kSmallLowercase) ? uppercase : true)
    self.layout?.layoutKeys(mode, uppercase: uppercase, characterUppercase: characterUppercase, shiftState: self.shiftState)
    
    self.setupKeys()
}

// 模式切换按键处理
func modeChangeTapped(_ sender: KeyboardKey) {
    if let toMode = self.layout?.viewToModel[sender]?.toMode {
        self.currentMode = toMode
    }
}

高级功能实现

1. 键盘主题切换

支持明/暗两种主题模式,根据系统设置自动切换:

// KeyboardLayout.swift
func updateAppearances(_ appearanceIsDark: Bool) {
    self.layout?.solidColorMode = self.solidColorMode()
    self.layout?.darkMode = appearanceIsDark
    self.layout?.updateKeyAppearance()
    
    self.bannerView?.darkMode = appearanceIsDark
    self.settingsView?.darkMode = appearanceIsDark
}

// 判断当前是否为暗黑模式
func darkMode() -> Bool {
    let proxy = self.textDocumentProxy
    return proxy.keyboardAppearance == UIKeyboardAppearance.dark
}

2. 用户设置界面

实现了包含多项设置的界面,允许用户自定义键盘行为:

// DefaultSettings.swift
class DefaultSettings: UIView {
    var autoCapitalizationSwitch: UISwitch!
    var periodShortcutSwitch: UISwitch!
    var keyboardClicksSwitch: UISwitch!
    var smallLowercaseSwitch: UISwitch!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
        loadSettings()
    }
    
    func setupViews() {
        // 创建并布局各个设置项...
        
        autoCapitalizationSwitch.addTarget(self, action: #selector(toggleSetting(_:)), for: .valueChanged)
        periodShortcutSwitch.addTarget(self, action: #selector(toggleSetting(_:)), for: .valueChanged)
        keyboardClicksSwitch.addTarget(self, action: #selector(toggleSetting(_:)), for: .valueChanged)
        smallLowercaseSwitch.addTarget(self, action: #selector(toggleSetting(_:)), for: .valueChanged)
    }
    
    func loadSettings() {
        let defaults = UserDefaults.standard
        autoCapitalizationSwitch.isOn = defaults.bool(forKey: kAutoCapitalization)
        periodShortcutSwitch.isOn = defaults.bool(forKey: kPeriodShortcut)
        keyboardClicksSwitch.isOn = defaults.bool(forKey: kKeyboardClicks)
        smallLowercaseSwitch.isOn = defaults.bool(forKey: kSmallLowercase)
    }
    
    @objc func toggleSetting(_ sender: UISwitch) {
        let defaults = UserDefaults.standard
        
        switch sender {
        case autoCapitalizationSwitch:
            defaults.set(sender.isOn, forKey: kAutoCapitalization)
        case periodShortcutSwitch:
            defaults.set(sender.isOn, forKey: kPeriodShortcut)
        case keyboardClicksSwitch:
            defaults.set(sender.isOn, forKey: kKeyboardClicks)
        case smallLowercaseSwitch:
            defaults.set(sender.isOn, forKey: kSmallLowercase)
            updateKeyCaps(self.shiftState.uppercase())
        default:
            break
        }
    }
}

常见问题与解决方案

1. 键盘高度适配问题

问题:不同设备和 orientations 下键盘高度不一致
解决方案:动态计算键盘高度,考虑设备类型和屏幕方向

// KeyboardViewController.swift
func height(forOrientation orientation: UIInterfaceOrientation, withTopBanner: Bool) -> CGFloat {
    let isPad = UIDevice.current.userInterfaceIdiom == .pad
    
    let actualScreenWidth = (UIScreen.main.nativeBounds.size.width / UIScreen.main.nativeScale)
    let canonicalPortraitHeight: CGFloat
    let canonicalLandscapeHeight: CGFloat
    
    if isPad {
        canonicalPortraitHeight = 264
        canonicalLandscapeHeight = 352
    } else {
        // 根据屏幕宽度适配不同设备
        canonicalPortraitHeight = orientation.isPortrait && actualScreenWidth >= 400 ? 226 : 216
        canonicalLandscapeHeight = 162
    }
    
    let topBannerHeight = withTopBanner ? metric("topBanner") : 0
    return orientation.isPortrait ? canonicalPortraitHeight + topBannerHeight : canonicalLandscapeHeight + topBannerHeight
}

2. 按键响应区域问题

问题:按键边缘区域点击无响应
解决方案:优化按键布局计算,确保按键之间无缝衔接

// 调整按键间距计算方式
class func keyGapPortrait(_ width: CGFloat, rowCharacterCount: Int) -> CGFloat {
    let compressed = (rowCharacterCount >= self.keyCompressedThreshhold)
    if compressed {
        return width >= self.keyGapPortraitUncompressThreshhold ? keyGapPortraitNormal : keyGapPortraitSmall
    } else {
        return keyGapPortraitNormal
    }
}

测试与调试技巧

1. 调试键盘扩展

自定义键盘作为扩展,调试方式与普通应用有所不同:

  1. 在Xcode中选择Keyboard目标
  2. 点击"Edit Scheme",设置"Executable"为HostingApp
  3. 在设备上运行,会自动启动HostingApp
  4. 在任意输入框点击,激活自定义键盘
  5. 设置断点进行调试

2. 测试不同设备适配

使用Xcode的模拟器测试不同设备和方向:

# 列出所有可用模拟器
xcrun simctl list devices

# 启动特定模拟器
open -a Simulator --args -CurrentDeviceUDID <device-udid>

发布与部署

1. 项目配置检查清单

  •  确保所有资源文件都已正确添加到目标
  •  检查Info.plist中的权限设置
  •  验证键盘扩展的NSExtension属性
  •  确保支持的iOS版本正确设置

2. App Store上架注意事项

  • 自定义键盘必须提供关闭选项
  • 确保不收集用户输入数据,或明确告知用户并获得许可
  • 键盘扩展不能包含广告
  • 必须支持所有iOS设备屏幕尺寸

扩展与定制方向

Tasty Imitation Keyboard作为一个基础框架,可以从以下几个方向进行扩展:

1. 添加表情符号键盘

func addEmojiKeyboard(to keyboard: Keyboard) {
    let emojiPage = 3
    
    // 添加常用表情符号
    let emojis = ["😀", "😃", "😄", "😁", "😆", "😅", "😂", "🤣", "😊", "😇"]
    for emoji in emojis {
        let key = Key(.specialCharacter)
        key.uppercaseOutput = emoji
        key.lowercaseOutput = emoji
        key.uppercaseKeyCap = emoji
        key.lowercaseKeyCap = emoji
        keyboard.add(key: key, row: 0, page: emojiPage)
    }
    
    // 添加切换到表情键盘的按钮
    let emojiModeButton = Key(.modeChange)
    emojiModeButton.uppercaseKeyCap = "😊"
    emojiModeButton.toMode = emojiPage
    keyboard.add(key: emojiModeButton, row: 3, page: 0)
    
    // 添加返回字母键盘的按钮
    let letterModeButton = Key(.modeChange)
    letterModeButton.uppercaseKeyCap = "ABC"
    letterModeButton.toMode = 0
    keyboard.add(key: letterModeButton, row: 3, page: emojiPage)
}

2. 实现手势输入

可以添加滑动输入功能,提升输入效率:

// 添加滑动手势识别
func setupSwipeGestures() {
    let swipeRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handleSwipe(_:)))
    swipeRecognizer.cancelsTouchesInView = false
    self.forwardingView.addGestureRecognizer(swipeRecognizer)
}

@objc func handleSwipe(_ recognizer: UIPanGestureRecognizer) {
    switch recognizer.state {
    case .began:
        // 记录滑动起点和当前按键
        let location = recognizer.location(in: self.forwardingView)
        startKey = keyAtLocation(location)
        currentKey = startKey
        
    case .changed:
        // 更新滑动位置和高亮按键
        let location = recognizer.location(in: self.forwardingView)
        let newKey = keyAtLocation(location)
        if newKey != currentKey {
            currentKey?.isHighlighted = false
            newKey?.isHighlighted = true
            currentKey = newKey
        }
        
    case .ended, .cancelled:
        // 输入当前按键并重置状态
        if let key = currentKey {
            keyPressedHelper(key)
        }
        currentKey?.isHighlighted = false
        startKey = nil
        currentKey = nil
        
    default:
        break
    }
}

总结与展望

Tasty Imitation Keyboard作为一个开源的自定义键盘项目,展示了iOS自定义键盘开发的核心技术和最佳实践。通过学习该项目,我们掌握了从键盘布局、按键交互到用户设置的完整实现流程。

未来可以进一步探索的方向:

  • 集成AI预测输入功能
  • 添加手写输入支持
  • 实现云同步用户配置
  • 支持多语言输入

希望本指南能帮助你快速入门iOS自定义键盘开发,并创造出功能强大、体验优秀的键盘应用!

参考资料

  1. Apple官方文档 - Custom Keyboard Extension
  2. iOS Custom Keyboard Development Tutorial
  3. Tasty Imitation Keyboard GitHub仓库

【免费下载链接】tasty-imitation-keyboard A custom keyboard for iOS8 that serves as a tasty imitation of the default Apple keyboard. Built using Swift and the latest Apple technologies! 【免费下载链接】tasty-imitation-keyboard 项目地址: https://gitcode.com/gh_mirrors/ta/tasty-imitation-keyboard

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值