iT邦幫忙

2022 iThome 鐵人賽

DAY 24
0
Mobile Development

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

D24 - 使用 SwiftUI 讓有趣的點子變成 Apps{無限猴子打字機: 為什麼 id 重複了?}

  • 分享至 

  • xImage
  •  

昨天,我們在 Xcode console 上一直看到這個 warning

ForEach<Array<String>, String, Text>: the ID q occurs multiple times within the collection, this will give undefined results!

在 List 那一段,我們使用 logs 本身當成 ID,而 logs 是型態為 [String],如果這個 array 裡面已經有 q,那再加上一個 q,就會有數個 q 了。而 ID 本身的意思就是「唯一值」,這時候值不唯一,就會噴出警告。(不過在 UI 上,仍然能正常顯示複數個 q,看來 List 有做其他的事情在 Array 更新的時候)

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

看來 String 在這裡不是個很好的資料結構,我們設計一個 TypingLog 來放進 logs 吧。這個 TypingLog 為了要能被 List 呈現,他需要 conform Identifiable 和 Hashable 這兩個 protocol。

struct TypingLog: Identifiable, Hashable {
    
    let id: UUID = .init()
    
    let typedString: String
}

然後修改一下 logs 的型別

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

再修改一下 list

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

再改一下 timer

/// 猴子開始打字囉
  private func askMonkeysTyping() {
    
    typingTimer = Timer.publish(every: 0.1, on: .main, in: .common)
      .autoconnect()
      .sink { _ in
        let typedCharacter = createRandomString()
        print("發動產生文字: \(typedCharacter)")
        // 如果想和 console 一樣,最新的在最下面,就用 append()
        let typingLog = TypingLog(typedString: typedCharacter)
        logs.insert(typingLog, at: 0)
      }
  }

然後,再 Build 起來讓猴子打個字,現在再也不會看到剛剛那個 warning 了。

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

整個 view 的程式碼如下

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: [TypingLog] = []
  
  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.typedString)"
        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()
        let typingLog = TypingLog(typedString: typedCharacter)
        logs.insert(typingLog, 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
  }
}

上一篇
D23 - 使用 SwiftUI 讓有趣的點子變成 Apps{無限猴子打字機: 打字紀錄}
下一篇
D25 - 使用 SwiftUI 讓有趣的點子變成 Apps{無限猴子打字機: 開始比對文字}
系列文
使用 SwiftUI 讓有趣的點子變成 Apps30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言