iT邦幫忙

2022 iThome 鐵人賽

DAY 23
0
Mobile Development

使用 SwiftUI 讓有趣的點子變成 Apps系列 第 23

D23 - 使用 SwiftUI 讓有趣的點子變成 Apps{無限猴子打字機: 打字紀錄}

  • 分享至 

  • xImage
  •  

前一天,我們把打出來的字,印在 console 上,不過使用者是看不到 console 的。為了讓使用者知道現在打出來的字,在 UI 上已經預留了一塊,讓紀錄留下的地方。

但是! 但是!! 但是!!!

當時只是畫一塊 Rectangle 而已,那時候留給未來的我來實作,而今天的我,就是來還過去的技術債的。

當時的 Rectangle 的程式

Rectangle()
        .foregroundColor(.white)
        .border(Color.blue)
        .padding()

我們現在開始重構這個紀錄區。

做出 logs 變數

先做出 logs 變數,並用 @State 修飾,List 元件就可以盯住這個 State,而在 logs 變化的時候變化。

@State private var logs: [String] = []

將 Rectangle() 換成 List()

			List(logs, id: \.self) { log in
        let eachLog = "打出的字為: \(log)"
        Text(eachLog)
      }

上面這段語法,是讓 每一個 logs 裡面的元素,在加上一段字後,放進 Text 內。

將 timer 輸出的字,放入 logs 內

/// 猴子開始打字囉
  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 不斷噴出。

https://ithelp.ithome.com.tw/upload/images/20220924/20140622EjS1P6vWpH.png

全部的程式碼如下

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.


上一篇
D22 - 使用 SwiftUI 讓有趣的點子變成 Apps{無限猴子打字機: 讓猴子開始敲鍵盤}
下一篇
D24 - 使用 SwiftUI 讓有趣的點子變成 Apps{無限猴子打字機: 為什麼 id 重複了?}
系列文
使用 SwiftUI 讓有趣的點子變成 Apps30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言