Skip to content

Commit

Permalink
replace UIKit with SwiftUI (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
eagleoflqj authored Jan 25, 2025
1 parent bf13851 commit f517c26
Show file tree
Hide file tree
Showing 14 changed files with 146 additions and 268 deletions.
2 changes: 1 addition & 1 deletion iosfrontend/iosfrontend.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import FcitxProtocol
import UIKit
import Foundation

public func commitStringAsync(_ clientPtr: UnsafeMutableRawPointer, _ commit: String) {
let client: AnyObject = Unmanaged.fromOpaque(clientPtr).takeUnretainedValue()
Expand Down
1 change: 1 addition & 0 deletions keyboard/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ target_include_directories(keyboard PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_CURRENT_BINARY_DIR}/../fcitx5"
"${PROJECT_BINARY_DIR}/common/$<CONFIG>${CMAKE_XCODE_EFFECTIVE_PLATFORMS}"
"${PROJECT_BINARY_DIR}/uipanel/$<CONFIG>${CMAKE_XCODE_EFFECTIVE_PLATFORMS}"
)

target_compile_options(keyboard PUBLIC
Expand Down
39 changes: 22 additions & 17 deletions keyboard/KeyboardViewController.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import Fcitx
import FcitxProtocol
import KeyboardUI
import SwiftUI
import SwiftUtil
import UIKit

class KeyboardViewController: UIInputViewController, FcitxProtocol {
var mainStackView: UIStackView!
var id: UInt64!
var hostingController: UIHostingController<VirtualKeyboardView>!

override func updateViewConstraints() {
super.updateViewConstraints()
Expand All @@ -17,24 +19,23 @@ class KeyboardViewController: UIInputViewController, FcitxProtocol {
id = UInt64(Int(bitPattern: Unmanaged.passUnretained(self).toOpaque()))
logger.info("viewDidLoad \(self.id)")
super.viewDidLoad()
initProfile()
startFcitx(Bundle.main.bundlePath, appGroup.path)

mainStackView = UIStackView()
mainStackView.axis = .vertical
mainStackView.alignment = .fill
hostingController = UIHostingController(rootView: virtualKeyboardView)
addChild(hostingController)

mainStackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mainStackView)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(hostingController.view)

// fill parent with no padding
NSLayoutConstraint.activate([
mainStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
mainStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0),
mainStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0),
mainStackView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0),
hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])

initProfile()
startFcitx(Bundle.main.bundlePath, appGroup.path)
hostingController.didMove(toParent: self)
}

override func viewWillAppear(_ animated: Bool) {
Expand All @@ -53,6 +54,14 @@ class KeyboardViewController: UIInputViewController, FcitxProtocol {
focusOut()
}

deinit {
logger.info("deinit \(self.id)")
hostingController.willMove(toParent: nil)
hostingController.view.removeFromSuperview()
hostingController.removeFromParent()
hostingController = nil
}

override func viewWillLayoutSubviews() {
logger.info("viewWillLayoutSubviews \(self.id)")
super.viewWillLayoutSubviews()
Expand All @@ -66,10 +75,6 @@ class KeyboardViewController: UIInputViewController, FcitxProtocol {
// The app has just changed the document's contents, the document context has been updated.
}

public func getView() -> UIStackView {
return mainStackView
}

public func keyPressed(_ key: String) {
if !processKey(key) {
textDocumentProxy.insertText(key)
Expand Down
4 changes: 0 additions & 4 deletions protocol/FcitxProtocol.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import UIKit

public protocol FcitxProtocol {
func getView() -> UIStackView
func keyPressed(_ key: String)
func commitString(_ string: String)
func setPreedit(_ preedit: String, _ cursor: Int)
func addChild(_ childController: UIViewController)
}
1 change: 1 addition & 0 deletions uipanel/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
add_library(KeyboardUI STATIC
keyboardui.swift
VirtualKeyboard.swift
Key.swift
Keyboard.swift
Candidate.swift
Expand Down
39 changes: 9 additions & 30 deletions uipanel/Candidate.swift
Original file line number Diff line number Diff line change
@@ -1,34 +1,13 @@
import UIKit
import SwiftUI
import UIPanel

class CandidateView: UICollectionViewCell {
static let identifier = "CandidateView"
struct CandidateView: View {
let text: String
let index: Int

let wordLabel: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 18)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()

override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(wordLabel)
setupConstraints()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

private func setupConstraints() {
NSLayoutConstraint.activate([
wordLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
wordLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
])
}

func configure(with word: String) {
wordLabel.text = word
var body: some View {
Text(text).font(.system(size: 20)).onTapGesture {
selectCandidate(Int32(index))
}
}
}
107 changes: 15 additions & 92 deletions uipanel/CandidateBar.swift
Original file line number Diff line number Diff line change
@@ -1,94 +1,17 @@
import UIKit
import UIPanel

class CandidateCollectionView: UIView {

var words = [String]()

private var collectionView: UICollectionView!

override init(frame: CGRect) {
super.init(frame: frame)
setupCollectionView()
setupConstraints()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

private func setupCollectionView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal

collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = UIColor.clear
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(
CandidateView.self, forCellWithReuseIdentifier: CandidateView.identifier)

addSubview(collectionView)
}

private func setupConstraints() {
NSLayoutConstraint.activate([
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor),
collectionView.topAnchor.constraint(equalTo: topAnchor),
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor),
collectionView.heightAnchor.constraint(equalToConstant: barHeight),
])
}

func updateCandidates(_ candidates: [String]) {
words = candidates
collectionView.reloadData()
}
}

extension CandidateCollectionView: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int)
-> Int
{
return words.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath)
-> UICollectionViewCell
{
guard
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: CandidateView.identifier, for: indexPath) as? CandidateView
else {
return UICollectionViewCell()
}
cell.configure(with: words[indexPath.item])
return cell
}
}

extension CandidateCollectionView: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectCandidate(Int32(indexPath.item))
}
}

extension CandidateCollectionView: UICollectionViewDelegateFlowLayout {
func collectionView(
_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath
) -> CGSize {
let word = words[indexPath.item]
let width = word.size(withAttributes: [.font: UIFont.systemFont(ofSize: 18)]).width + 20
return CGSize(width: width, height: 35)
}

func collectionView(
_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int
) -> CGFloat {
return 10
import SwiftUI

struct CandidateBarView: View {
@Binding var candidates: [String]

var body: some View {
ScrollView(.horizontal) {
HStack(spacing: 20) {
ForEach(Array(candidates.enumerated()), id: \.offset) { index, candidate in
CandidateView(text: candidate, index: index)
}
Spacer()
}.frame(height: barHeight)
}.scrollIndicators(.hidden) // Hide scroll bar as native keyboard.
.padding([.leading], 10)
}
}
37 changes: 15 additions & 22 deletions uipanel/Key.swift
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
import FcitxProtocol
import UIKit
import SwiftUI

class Key: UIButton {
let client: FcitxProtocol
struct KeyView: View {
let label: String

init(_ client: FcitxProtocol, _ label: String) {
self.client = client
super.init(frame: .zero)
setTitle(label, for: .normal)
titleLabel?.font = UIFont.systemFont(ofSize: 24)
backgroundColor = UIColor.gray.withAlphaComponent(0.2)
layer.cornerRadius = 8
addTarget(self, action: #selector(keyPressed(_:)), for: .touchUpInside)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

@objc private func keyPressed(_ sender: UIButton) {
guard let currentTitle = sender.currentTitle else {
return
var body: some View {
Button {
virtualKeyboardView.keyPressed(label)
} label: {
Text(label)
.frame(width: label == " " ? 100 : 35, height: 40)
.background(Color.white)
.cornerRadius(5)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.gray, lineWidth: 1)
)
}
client.keyPressed(currentTitle)
}
}
44 changes: 11 additions & 33 deletions uipanel/Keyboard.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import FcitxProtocol
import UIKit

class Keyboard: UIStackView {
let client: FcitxProtocol
import SwiftUI

struct KeyboardView: View {
let keys: [[String]] = [
["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"],
["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"],
Expand All @@ -12,34 +9,15 @@ class Keyboard: UIStackView {
[",", " ", "."],
]

init(_ client: FcitxProtocol) {
self.client = client
super.init(frame: .zero)
setupKeyboardLayout()
}

required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

private func setupKeyboardLayout() {
axis = .vertical
distribution = .fillEqually
alignment = .fill
spacing = 5

for row in keys {
let rowStackView = UIStackView()
rowStackView.axis = .horizontal
rowStackView.distribution = .fillEqually
rowStackView.alignment = .fill
rowStackView.spacing = 5

for key in row {
let button = Key(client, key)
rowStackView.addArrangedSubview(button)
var body: some View {
VStack(spacing: 8) {
ForEach(keys, id: \.self) { row in
HStack(spacing: 6) {
ForEach(row, id: \.self) { key in
KeyView(label: key)
}
}
}
addArrangedSubview(rowStackView)
}
}.frame(height: keyboardHeight)
}
}
Loading

0 comments on commit f517c26

Please sign in to comment.