前一天,我們把打出來的字,印在 console 上,不過使用者是看不到 console 的。為了讓使用者知道現在打出來的字,在 UI 上已經預留了一塊,讓紀錄留下的地方。
但是! 但是!! 但是!!!
當時只是畫一塊 Rectangle 而已,那時候留給未來的我來實作,而今天的我,就是來還過去的技術債的。
當時的 Rectangle 的程式
Rectangle()
.foregroundColor(.white)
.border(Color.blue)
.padding()
我們現在開始重構這個紀錄區。
先做出 logs 變數,並用 @State 修飾,List 元件就可以盯住這個 State,而在 logs 變化的時候變化。
@State private var logs: [String] = []
List(logs, id: \.self) { log in
let eachLog = "打出的字為: \(log)"
Text(eachLog)
}
上面這段語法,是讓 每一個 logs 裡面的元素,在加上一段字後,放進 Text 內。
/// 猴子開始打字囉
private func askMonkeysTyping() {
typingTimer = Timer.publish(every: 0.1, on: .main, in: .common)
.autoconnect()
.sink { _ in
let typedCharacter = createRandomString()
print("發動產生文字: \(typedCharacter)")
// 如果想和 console 一樣,最新的在最下面,就用 append()
logs.insert(typedCharacter, at: 0)
}
}
然後 build 起來後,按下打字 Button,就可以看到 logs 不斷噴出。
全部的程式碼如下
import SwiftUI
import Combine
struct InfiniteMonkeyTypingContentView: View {
@State private var targetText = ""
@State private var monkeyTyperCount = 1
@State private var logText = ""
@State private var textStyle = UIFont.TextStyle.body
/// 發動「猴子」打字的 timer
@State private var typingTimer: AnyCancellable?
@State private var logs: [String] = []
private var targetHint: String {
if targetText.isEmpty {
return "目前沒目標,請輸入目標文字在輸入框"
}
return "你的目標為: \(targetText)"
}
var body: some View {
VStack {
Text("無限猴子打字機")
.font(.largeTitle)
.padding(.top, 20)
Text(targetHint)
.lineLimit(1)
.padding()
TextField("請輸入目標", text: $targetText)
.autocapitalization(.none)
.padding()
.textFieldStyle(.roundedBorder)
monkeyTyperStepper
monkeyActionButtons
monkeyLogsAndClearLogs
List(logs, id: \.self) { log in
let eachLog = "打出的字為: \(log)"
Text(eachLog)
}
Spacer()
}
}
private var monkeyTyperStepper: some View {
HStack {
Stepper("猴子數: \(monkeyTyperCount)") {
stepperIncrease()
} onDecrement: {
stepperDecrease()
}
}
.padding()
}
private var monkeyActionButtons: some View {
HStack {
Button("猴子停手") {
stopMonkeysTyping()
}
Button("叫猴子開始打字囉") {
askMonkeysTyping()
}
}
.buttonStyle(.bordered)
.padding()
}
private var monkeyLogsAndClearLogs: some View {
HStack {
Spacer()
Text("猴子的打字紀錄")
Button {
// TODO: - 清掉打字
} label: {
Text("清除打字紀錄")
}
.padding(.leading, 20)
.buttonStyle(.bordered)
Spacer()
}
}
private func stepperIncrease() {
monkeyTyperCount += 1
}
private func stepperDecrease() {
if monkeyTyperCount > 1 {
monkeyTyperCount -= 1
}
}
/// 猴子開始打字囉
private func askMonkeysTyping() {
typingTimer = Timer.publish(every: 0.1, on: .main, in: .common)
.autoconnect()
.sink { _ in
let typedCharacter = createRandomString()
print("發動產生文字: \(typedCharacter)")
// 如果想和 console 一樣,最新的在最下面,就用 append()
logs.insert(typedCharacter, at: 0)
}
}
/// 叫猴子停手
private func stopMonkeysTyping() {
typingTimer?.cancel()
}
}
extension InfiniteMonkeyTypingContentView {
private var alphabet: [String] {
let characters = "abcdefghijklmnopqrstuvwxyz"
var chars: [String] = []
for char in characters {
chars.append(String(char))
}
return chars
}
private func createRandomString() -> String {
var resultString = ""
for _ in 0..<monkeyTyperCount {
resultString += alphabet.randomElement() ?? ""
}
return resultString
}
}
不過…你應該會看到 Xcode 一直出現這段 message。
ForEach<Array<String>, String, Text>: the ID a occurs multiple times within the collection, this will give undefined results!
發動產生文字: j
ForEach<Array<String>, String, Text>: the ID j occurs multiple times within the collection, this will give undefined results!
發動產生文字: x
發動產生文字: q
發動產生文字: q
ForEach<Array<String>, String, Text>: the ID q occurs multiple times within the collection, this will give undefined results!
發動產生文字: j
我們明天來解釋為什麼,以及如何避開這段 warning.