iT邦幫忙

2022 iThome 鐵人賽

DAY 13
0
Mobile Development

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

D13 - 使用 SwiftUI 讓有趣的點子變成 Apps{葛麗絲逆走鐘: 機芯 ObservableObject}

  • 分享至 

  • xImage
  •  

在 D4 的文章,我本來設計了三個物件

  • 時針、分針、秒針
  • 錶盤
  • 動力來源(真實世界的機芯)

目前完成了上面兩個,也讓這個 app 跑起來,不過,我還是想要有個「物件」是這個時鐘的動力來源,試著和真實世界的時鐘一樣有個動力來源。

機芯的英文為 clockwork,接下來就設計 clockwork 物件。

需求分析

  • 機芯是可以開/關的,就和現實世界放進電池/拿出電池的狀態一樣
  • 機芯能知道 client 端的時間,但不一定要讓外部訪問
  • 機芯在運作的時候,要告訴外部 時針、分針、秒針 的角度

Combine & ObservableObject

要讓一個物件可以在 Combine 框架下,被別的物件觀察到。在這個框架的設計下,不用讓整個物件被觀察,可以使用 @Published 這個 property wrapper 讓特定 property 開出去讓別的物件 binding。

這個機芯,會有幾個 func 可以讓別人操作

  • startTimer() - 啟動計時,在 init() 當下,就會發動 timer
  • stopTimer() - 如果真的想停,也可以停下來,模仿真實世界的機芯可進行的行為
  • secondAngle - 秒針角度 (degree)
  • minuteAngle - 分針角度 (degree)
  • hourAngle - 時針角度 (degree)
// 機芯
class Clockwork: ObservableObject {
    
    var timer: Cancellable?
    
    var timestamp: TimeInterval = 0
    
    @Published var secondAngle: Double = .zero
    
    @Published var minuteAngle: Double = .zero
    
    @Published var hourAngle: Double = .zero
    
    private var angleUtility: AngleUtility = .init()
    
    private var dateUtility: DateUtility = .init()
    
    init() {
        startTimer()
    }
    
    func stopTimer() {
        timer?.cancel()
    }
    
    func startTimer() {
        timer = Timer
            .publish(every: 0.1, on: .main, in: .common)
            .autoconnect()
            .sink { [weak self] _ in
                self?.updateTime()
            }
    }
    
    private func updateTime() {
        let timestamp = Date().timeIntervalSince1970
        calculateAngle(from: timestamp)
        self.timestamp = timestamp
    }
    
    private func calculateAngle(from timeInterval: TimeInterval) {
        
        secondAngle = angleUtility.getBackwardsSecondHandRadius(from: timeInterval)
        minuteAngle = angleUtility.getBackwardsMinuteHandRadius(from: timeInterval)
        hourAngle = angleUtility.getBackwardsHourHandRadius(from: timeInterval)
    }
    
    private func update(timeInterval: TimeInterval) {
        timestamp = timeInterval
        print("timestamp updated: \(timestamp)")
    }
}

然後,我們來把「機芯」「裝上去」,裝在 View 上。

struct ClockContainerView: View {
  
  var width: CGFloat = 200
  var height: CGFloat = 200
  
  @StateObject private var clockwork: Clockwork = .init()
  
  private let angleUtility: AngleUtility = .init()
  
  var body: some View {
    ZStack {
      ClockDialView()
      HandShape(handLength: .hour)
        .fill(Color.blue)
        .rotationEffect(Angle(degrees: clockwork.hourAngle))
      HandShape(handLength: .minute)
        .fill(Color.cyan)
        .rotationEffect(Angle(degrees: clockwork.minuteAngle))
      HandShape(handLength: .second)
        .fill(Color.red)
        .rotationEffect(Angle(degrees: clockwork.secondAngle))
      Circle()
        .fill(Color.orange)
        .frame(width: 20, height: 20, alignment: .center)
    }
    .frame(width: width, height: height, alignment: .center)
  }
  
}

Run 起來看一下 clock 長什麼樣子,和原來一樣,所以我們完成了機芯物件的設計,也沒破壞掉專案來可以動的部分。

https://ithelp.ithome.com.tw/upload/images/20220913/20140622JqUiqqAkNF.png


上一篇
D12 - 使用 SwiftUI 讓有趣的點子變成 Apps{葛麗絲逆走鐘: 把角度計算器放進 View 裡面}
下一篇
D14 - 使用 SwiftUI 讓有趣的點子變成 Apps{葛麗絲逆走鐘: 加上 WebView 讀取外部網頁}
系列文
使用 SwiftUI 讓有趣的點子變成 Apps30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言