Skip to content

Commit ebe4d78

Browse files
authored
[LOOP-4884] Use LoopCircleView for LoopStateView / SwiftUI Interop
2 parents 030035b + a601a2f commit ebe4d78

File tree

3 files changed

+89
-106
lines changed

3 files changed

+89
-106
lines changed

Loop/View Controllers/StatusTableViewController.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,7 @@ final class StatusTableViewController: LoopChartsTableViewController {
982982
case .hud:
983983
let cell = tableView.dequeueReusableCell(withIdentifier: HUDViewTableViewCell.className, for: indexPath) as! HUDViewTableViewCell
984984
hudView = cell.hudView
985+
cell.hudView.loopCompletionHUD.loopStatusColors = .loopStatus
985986

986987
return cell
987988
case .charts:

LoopUI/Views/LoopCompletionHUDView.swift

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public final class LoopCompletionHUDView: BaseHUDView {
2020

2121
private(set) var freshness = LoopCompletionFreshness.stale {
2222
didSet {
23-
updateTintColor()
23+
loopStateView.freshness = freshness
2424
}
2525
}
2626

@@ -30,6 +30,12 @@ public final class LoopCompletionHUDView: BaseHUDView {
3030
updateDisplay(nil)
3131
}
3232

33+
public var loopStatusColors: StateColorPalette = StateColorPalette(unknown: .black, normal: .black, warning: .black, error: .black) {
34+
didSet {
35+
loopStateView.loopStatusColors = loopStatusColors
36+
}
37+
}
38+
3339
public var loopIconClosed = false {
3440
didSet {
3541
loopStateView.open = !loopIconClosed
@@ -65,26 +71,6 @@ public final class LoopCompletionHUDView: BaseHUDView {
6571
}
6672
}
6773

68-
override public func stateColorsDidUpdate() {
69-
super.stateColorsDidUpdate()
70-
updateTintColor()
71-
}
72-
73-
private var _tintColor: UIColor? {
74-
switch freshness {
75-
case .fresh:
76-
return stateColors?.normal
77-
case .aging:
78-
return stateColors?.warning
79-
case .stale:
80-
return stateColors?.error
81-
}
82-
}
83-
84-
private func updateTintColor() {
85-
self.tintColor = _tintColor
86-
}
87-
8874
private func initTimer(_ startDate: Date) {
8975
let updateInterval = TimeInterval(minutes: 1)
9076

LoopUI/Views/LoopStateView.swift

Lines changed: 81 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -6,113 +6,109 @@
66
// Copyright © 2016 Nathan Racklyeft. All rights reserved.
77
//
88

9+
import LoopKit
10+
import LoopKitUI
11+
import SwiftUI
912
import UIKit
1013

11-
final class LoopStateView: UIView {
12-
var firstDataUpdate = true
14+
class WrappedLoopStateViewModel: ObservableObject {
15+
@Published var loopStatusColors: StateColorPalette
16+
@Published var closedLoop: Bool
17+
@Published var freshness: LoopCompletionFreshness
18+
@Published var animating: Bool
1319

14-
override func tintColorDidChange() {
15-
super.tintColorDidChange()
16-
17-
updateTintColor()
20+
init(
21+
loopStatusColors: StateColorPalette = StateColorPalette(unknown: .black, normal: .black, warning: .black, error: .black),
22+
closedLoop: Bool = true,
23+
freshness: LoopCompletionFreshness = .stale,
24+
animating: Bool = false
25+
) {
26+
self.loopStatusColors = loopStatusColors
27+
self.closedLoop = closedLoop
28+
self.freshness = freshness
29+
self.animating = animating
1830
}
31+
}
1932

20-
private func updateTintColor() {
21-
shapeLayer.strokeColor = tintColor.cgColor
33+
struct WrappedLoopCircleView: View {
34+
35+
@ObservedObject var viewModel: WrappedLoopStateViewModel
36+
37+
var body: some View {
38+
LoopCircleView(closedLoop: viewModel.closedLoop, freshness: viewModel.freshness, animating: viewModel.animating)
39+
.environment(\.loopStatusColorPalette, viewModel.loopStatusColors)
2240
}
41+
}
2342

24-
var open = false {
25-
didSet {
26-
if open != oldValue {
27-
if open, animated {
28-
animated = false
29-
}
30-
shapeLayer.path = drawPath()
31-
}
32-
}
43+
class LoopCircleHostingController: UIHostingController<WrappedLoopCircleView> {
44+
init(viewModel: WrappedLoopStateViewModel) {
45+
super.init(
46+
rootView: WrappedLoopCircleView(
47+
viewModel: viewModel
48+
)
49+
)
3350
}
34-
35-
override class var layerClass : AnyClass {
36-
return CAShapeLayer.self
51+
52+
required init?(coder aDecoder: NSCoder) {
53+
fatalError()
3754
}
55+
}
3856

39-
private var shapeLayer: CAShapeLayer {
40-
return layer as! CAShapeLayer
41-
}
4257

58+
final class LoopStateView: UIView {
59+
4360
override init(frame: CGRect) {
4461
super.init(frame: frame)
45-
46-
shapeLayer.lineWidth = 8
47-
shapeLayer.fillColor = UIColor.clear.cgColor
48-
updateTintColor()
49-
50-
shapeLayer.path = drawPath()
62+
63+
setupViews()
5164
}
52-
53-
required init?(coder aDecoder: NSCoder) {
54-
super.init(coder: aDecoder)
55-
56-
shapeLayer.lineWidth = 8
57-
shapeLayer.fillColor = UIColor.clear.cgColor
58-
updateTintColor()
59-
60-
shapeLayer.path = drawPath()
65+
66+
required init?(coder: NSCoder) {
67+
super.init(coder: coder)
68+
69+
setupViews()
6170
}
62-
63-
override func layoutSubviews() {
64-
super.layoutSubviews()
65-
66-
shapeLayer.path = drawPath()
71+
72+
var loopStatusColors: StateColorPalette = StateColorPalette(unknown: .black, normal: .black, warning: .black, error: .black) {
73+
didSet {
74+
viewModel.loopStatusColors = loopStatusColors
75+
}
6776
}
6877

69-
private func drawPath(lineWidth: CGFloat? = nil) -> CGPath {
70-
let center = CGPoint(x: bounds.midX, y: bounds.midY)
71-
let lineWidth = lineWidth ?? shapeLayer.lineWidth
72-
let radius = min(bounds.width / 2, bounds.height / 2) - lineWidth / 2
73-
74-
let startAngle = open ? -CGFloat.pi / 4 : 0
75-
let endAngle = open ? 5 * CGFloat.pi / 4 : 2 * CGFloat.pi
76-
77-
let path = UIBezierPath(
78-
arcCenter: center,
79-
radius: radius,
80-
startAngle: startAngle,
81-
endAngle: endAngle,
82-
clockwise: true
83-
)
84-
85-
return path.cgPath
78+
var freshness: LoopCompletionFreshness = .stale {
79+
didSet {
80+
viewModel.freshness = freshness
81+
}
82+
}
83+
84+
var open = false {
85+
didSet {
86+
viewModel.closedLoop = !open
87+
}
8688
}
87-
88-
private static let AnimationKey = "com.loudnate.Naterade.breatheAnimation"
8989

9090
var animated: Bool = false {
9191
didSet {
92-
if animated != oldValue {
93-
if animated, !open {
94-
let path = CABasicAnimation(keyPath: "path")
95-
path.fromValue = shapeLayer.path ?? drawPath()
96-
path.toValue = drawPath(lineWidth: 16)
97-
98-
let width = CABasicAnimation(keyPath: "lineWidth")
99-
width.fromValue = shapeLayer.lineWidth
100-
width.toValue = 10
101-
102-
let group = CAAnimationGroup()
103-
group.animations = [path, width]
104-
group.duration = firstDataUpdate ? 0 : 1
105-
group.repeatCount = HUGE
106-
group.autoreverses = true
107-
group.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
108-
109-
shapeLayer.add(group, forKey: type(of: self).AnimationKey)
110-
} else {
111-
shapeLayer.removeAnimation(forKey: type(of: self).AnimationKey)
112-
}
113-
}
114-
firstDataUpdate = false
92+
viewModel.animating = animated
11593
}
11694
}
95+
96+
private let viewModel = WrappedLoopStateViewModel()
97+
98+
private func setupViews() {
99+
let hostingController = LoopCircleHostingController(viewModel: viewModel)
100+
101+
hostingController.view.backgroundColor = .clear
102+
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
103+
104+
addSubview(hostingController.view)
105+
106+
NSLayoutConstraint.activate([
107+
hostingController.view.leadingAnchor.constraint(equalTo: leadingAnchor),
108+
hostingController.view.trailingAnchor.constraint(equalTo: trailingAnchor),
109+
hostingController.view.topAnchor.constraint(equalTo: topAnchor),
110+
hostingController.view.bottomAnchor.constraint(equalTo: bottomAnchor)
111+
])
112+
}
117113
}
118114

0 commit comments

Comments
 (0)