forked from zelo72/mastodon-ios
feat: add animation for progress bar value change
This commit is contained in:
parent
06aac878c8
commit
58c8eaabe8
|
@ -129,7 +129,7 @@
|
|||
DB59F0FE25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F0FD25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift */; };
|
||||
DB59F10425EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F10325EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift */; };
|
||||
DB59F10E25EF724F001F1DAB /* APIService+Poll.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F10D25EF724F001F1DAB /* APIService+Poll.swift */; };
|
||||
DB59F11825EFA35B001F1DAB /* VoteProgressStripView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F11725EFA35B001F1DAB /* VoteProgressStripView.swift */; };
|
||||
DB59F11825EFA35B001F1DAB /* StripProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F11725EFA35B001F1DAB /* StripProgressView.swift */; };
|
||||
DB68586425E619B700F0A850 /* NSKeyValueObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */; };
|
||||
DB68A04A25E9027700CFDF14 /* DarkContentStatusBarStyleNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68A04925E9027700CFDF14 /* DarkContentStatusBarStyleNavigationController.swift */; };
|
||||
DB68A05D25E9055900CFDF14 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DB68A05C25E9055900CFDF14 /* Settings.bundle */; };
|
||||
|
@ -358,7 +358,7 @@
|
|||
DB59F0FD25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusProvider+UITableViewDelegate.swift"; sourceTree = "<group>"; };
|
||||
DB59F10325EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewCellHeightCacheableContainer.swift; sourceTree = "<group>"; };
|
||||
DB59F10D25EF724F001F1DAB /* APIService+Poll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Poll.swift"; sourceTree = "<group>"; };
|
||||
DB59F11725EFA35B001F1DAB /* VoteProgressStripView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoteProgressStripView.swift; sourceTree = "<group>"; };
|
||||
DB59F11725EFA35B001F1DAB /* StripProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripProgressView.swift; sourceTree = "<group>"; };
|
||||
DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSKeyValueObservation.swift; sourceTree = "<group>"; };
|
||||
DB68A04925E9027700CFDF14 /* DarkContentStatusBarStyleNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DarkContentStatusBarStyleNavigationController.swift; sourceTree = "<group>"; };
|
||||
DB68A05C25E9055900CFDF14 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
||||
|
@ -527,7 +527,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
2D152A8B25C295CC009AA50C /* StatusView.swift */,
|
||||
DB59F11725EFA35B001F1DAB /* VoteProgressStripView.swift */,
|
||||
);
|
||||
path = Content;
|
||||
sourceTree = "<group>";
|
||||
|
@ -684,6 +683,7 @@
|
|||
2D42FF8325C82245004A627A /* Button */,
|
||||
2D42FF7C25C82207004A627A /* ToolBar */,
|
||||
DB9D6C1325E4F97A0051B173 /* Container */,
|
||||
DBA9B90325F1D4420012E7B6 /* Control */,
|
||||
2D152A8A25C295B8009AA50C /* Content */,
|
||||
DB1D187125EF5BBD003F1F23 /* TableView */,
|
||||
2D7631A625C1533800929FB9 /* TableviewCell */,
|
||||
|
@ -1119,6 +1119,14 @@
|
|||
path = ViewModel;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DBA9B90325F1D4420012E7B6 /* Control */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB59F11725EFA35B001F1DAB /* StripProgressView.swift */,
|
||||
);
|
||||
path = Control;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DBE0821A25CD382900FD6BBD /* Register */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1523,7 +1531,7 @@
|
|||
DBD9149025DF6D8D00903DFD /* APIService+Onboarding.swift in Sources */,
|
||||
DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */,
|
||||
2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */,
|
||||
DB59F11825EFA35B001F1DAB /* VoteProgressStripView.swift in Sources */,
|
||||
DB59F11825EFA35B001F1DAB /* StripProgressView.swift in Sources */,
|
||||
DB59F10425EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift in Sources */,
|
||||
DBE0822425CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift in Sources */,
|
||||
2DF75B9B25D0E27500694EC8 /* StatusProviderFacade.swift in Sources */,
|
||||
|
|
|
@ -76,7 +76,7 @@ extension PollSection {
|
|||
cell.optionPercentageLabel.isHidden = false
|
||||
cell.optionPercentageLabel.text = String(Int(100 * percentage)) + "%"
|
||||
cell.voteProgressStripView.tintColor = voted ? Asset.Colors.Background.Poll.highlight.color : Asset.Colors.Background.Poll.disabled.color
|
||||
cell.voteProgressStripView.progress.send(CGFloat(percentage))
|
||||
cell.voteProgressStripView.setProgress(CGFloat(percentage), animated: true)
|
||||
}
|
||||
cell.voteState = state
|
||||
|
||||
|
|
|
@ -302,8 +302,6 @@ extension StatusSection {
|
|||
return Double(option.votesCount?.intValue ?? 0) / Double(poll.votesCount.intValue)
|
||||
}()
|
||||
let voted = votedOptions.isEmpty ? true : votedOptions.contains(option)
|
||||
// let voted = true
|
||||
// let percentage: Double = Double.random(in: 0..<1)
|
||||
return .reveal(voted: voted, percentage: percentage)
|
||||
}()
|
||||
return PollItem.Attribute(selectState: selectState, voteState: voteState)
|
||||
|
|
|
@ -1,139 +0,0 @@
|
|||
//
|
||||
// VoteProgressStripView.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-3.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
final class VoteProgressStripView: UIView {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
private lazy var stripLayer: CAShapeLayer = {
|
||||
let shapeLayer = CAShapeLayer()
|
||||
shapeLayer.lineCap = .round
|
||||
shapeLayer.fillColor = tintColor.cgColor
|
||||
shapeLayer.strokeColor = UIColor.clear.cgColor
|
||||
return shapeLayer
|
||||
}()
|
||||
|
||||
let progressMaskLayer: CAShapeLayer = {
|
||||
let shapeLayer = CAShapeLayer()
|
||||
shapeLayer.fillColor = UIColor.red.cgColor
|
||||
return shapeLayer
|
||||
}()
|
||||
|
||||
let progress = CurrentValueSubject<CGFloat, Never>(0.0)
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
_init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
_init()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension VoteProgressStripView {
|
||||
|
||||
private func _init() {
|
||||
updateLayerPath()
|
||||
|
||||
layer.addSublayer(stripLayer)
|
||||
|
||||
progress
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] progress in
|
||||
guard let self = self else { return }
|
||||
UIView.animate(withDuration: 0.33) {
|
||||
self.updateLayerPath()
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
updateLayerPath()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension VoteProgressStripView {
|
||||
private func updateLayerPath() {
|
||||
guard bounds != .zero else { return }
|
||||
|
||||
stripLayer.frame = bounds
|
||||
stripLayer.fillColor = tintColor.cgColor
|
||||
|
||||
stripLayer.path = {
|
||||
let path = UIBezierPath(roundedRect: bounds, cornerRadius: 0)
|
||||
return path.cgPath
|
||||
}()
|
||||
|
||||
|
||||
progressMaskLayer.path = {
|
||||
var rect = bounds
|
||||
let newWidth = progress.value * rect.width
|
||||
let widthChanged = rect.width - newWidth
|
||||
rect.size.width = newWidth
|
||||
switch UIApplication.shared.userInterfaceLayoutDirection {
|
||||
case .rightToLeft:
|
||||
rect.origin.x += widthChanged
|
||||
default:
|
||||
break
|
||||
}
|
||||
let path = UIBezierPath(rect: rect)
|
||||
return path.cgPath
|
||||
}()
|
||||
stripLayer.mask = progressMaskLayer
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#if DEBUG
|
||||
import SwiftUI
|
||||
|
||||
struct VoteProgressStripView_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
UIViewPreview() {
|
||||
VoteProgressStripView()
|
||||
}
|
||||
.frame(width: 100, height: 44)
|
||||
.padding()
|
||||
.background(Color.black)
|
||||
.previewLayout(.sizeThatFits)
|
||||
UIViewPreview() {
|
||||
let bar = VoteProgressStripView()
|
||||
bar.tintColor = .white
|
||||
bar.progress.value = 0.5
|
||||
return bar
|
||||
}
|
||||
.frame(width: 100, height: 44)
|
||||
.padding()
|
||||
.background(Color.black)
|
||||
.previewLayout(.sizeThatFits)
|
||||
UIViewPreview() {
|
||||
let bar = VoteProgressStripView()
|
||||
bar.tintColor = .white
|
||||
bar.progress.value = 1.0
|
||||
return bar
|
||||
}
|
||||
.frame(width: 100, height: 44)
|
||||
.padding()
|
||||
.background(Color.black)
|
||||
.previewLayout(.sizeThatFits)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,165 @@
|
|||
//
|
||||
// StripProgressView.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-3.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
private final class StripProgressLayer: CALayer {
|
||||
|
||||
var tintColor: UIColor = .black
|
||||
@NSManaged var progress: CGFloat
|
||||
|
||||
override class func needsDisplay(forKey key: String) -> Bool {
|
||||
switch key {
|
||||
case "progress":
|
||||
return true
|
||||
default:
|
||||
return super.needsDisplay(forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
override func display() {
|
||||
let progress = presentation()?.progress ?? self.progress
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: progress: %.2f", ((#file as NSString).lastPathComponent), #line, #function, progress)
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0)
|
||||
guard let context = UIGraphicsGetCurrentContext() else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
context.clear(bounds)
|
||||
|
||||
var rect = bounds
|
||||
let newWidth = CGFloat(progress) * rect.width
|
||||
let widthChanged = rect.width - newWidth
|
||||
rect.size.width = newWidth
|
||||
switch UIApplication.shared.userInterfaceLayoutDirection {
|
||||
case .rightToLeft:
|
||||
rect.origin.x += widthChanged
|
||||
default:
|
||||
break
|
||||
}
|
||||
let path = UIBezierPath(rect: rect)
|
||||
context.setFillColor(tintColor.cgColor)
|
||||
context.addPath(path.cgPath)
|
||||
context.fillPath()
|
||||
|
||||
contents = UIGraphicsGetImageFromCurrentImageContext()?.cgImage
|
||||
UIGraphicsEndImageContext()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final class StripProgressView: UIView {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
private let stripProgressLayer: StripProgressLayer = {
|
||||
let layer = StripProgressLayer()
|
||||
return layer
|
||||
}()
|
||||
|
||||
override var tintColor: UIColor! {
|
||||
didSet {
|
||||
stripProgressLayer.tintColor = tintColor
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
func setProgress(_ progress: CGFloat, animated: Bool) {
|
||||
stripProgressLayer.removeAnimation(forKey: "progressAnimationKey")
|
||||
if animated {
|
||||
let animation = CABasicAnimation(keyPath: "progress")
|
||||
animation.fromValue = stripProgressLayer.presentation()?.progress ?? stripProgressLayer.progress
|
||||
animation.toValue = progress
|
||||
animation.duration = 0.33
|
||||
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
||||
animation.isRemovedOnCompletion = true
|
||||
stripProgressLayer.add(animation, forKey: "progressAnimationKey")
|
||||
stripProgressLayer.progress = progress
|
||||
} else {
|
||||
stripProgressLayer.progress = progress
|
||||
stripProgressLayer.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
_init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
_init()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension StripProgressView {
|
||||
|
||||
private func _init() {
|
||||
layer.addSublayer(stripProgressLayer)
|
||||
updateLayerPath()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
updateLayerPath()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension StripProgressView {
|
||||
private func updateLayerPath() {
|
||||
guard bounds != .zero else { return }
|
||||
|
||||
stripProgressLayer.frame = bounds
|
||||
stripProgressLayer.tintColor = tintColor
|
||||
stripProgressLayer.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
import SwiftUI
|
||||
|
||||
struct VoteProgressStripView_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
UIViewPreview() {
|
||||
StripProgressView()
|
||||
}
|
||||
.frame(width: 100, height: 44)
|
||||
.padding()
|
||||
.background(Color.black)
|
||||
.previewLayout(.sizeThatFits)
|
||||
UIViewPreview() {
|
||||
let bar = StripProgressView()
|
||||
bar.tintColor = .white
|
||||
bar.setProgress(0.5, animated: false)
|
||||
return bar
|
||||
}
|
||||
.frame(width: 100, height: 44)
|
||||
.padding()
|
||||
.background(Color.black)
|
||||
.previewLayout(.sizeThatFits)
|
||||
UIViewPreview() {
|
||||
let bar = StripProgressView()
|
||||
bar.tintColor = .white
|
||||
bar.setProgress(1.0, animated: false)
|
||||
return bar
|
||||
}
|
||||
.frame(width: 100, height: 44)
|
||||
.padding()
|
||||
.background(Color.black)
|
||||
.previewLayout(.sizeThatFits)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
|
@ -20,8 +20,8 @@ final class PollOptionTableViewCell: UITableViewCell {
|
|||
var voteState: PollItem.Attribute.VoteState?
|
||||
|
||||
let roundedBackgroundView = UIView()
|
||||
let voteProgressStripView: VoteProgressStripView = {
|
||||
let view = VoteProgressStripView()
|
||||
let voteProgressStripView: StripProgressView = {
|
||||
let view = StripProgressView()
|
||||
view.tintColor = Asset.Colors.Background.Poll.highlight.color
|
||||
return view
|
||||
}()
|
||||
|
|
Loading…
Reference in New Issue