Merge branch 'release/7_0' into 'develop'

Release/7 0

See merge request BPHV_MIPS/mvm_core_ui!167
This commit is contained in:
Pan, Xinlei (Ryan) 2019-11-06 11:08:05 -05:00
commit 10fe33748f
4 changed files with 321 additions and 0 deletions

View File

@ -28,6 +28,8 @@
0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; };
0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; };
0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; };
943784F5236B77BB006A1E82 /* GraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F3236B77BB006A1E82 /* GraphView.swift */; };
943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */; };
9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; };
948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; };
D206997721FB8A0B00CAE0DE /* MVMCoreUINavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -223,6 +225,8 @@
0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyButton.swift; sourceTree = "<group>"; };
0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = "<group>"; };
0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = "<group>"; };
943784F3236B77BB006A1E82 /* GraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphView.swift; sourceTree = "<group>"; };
943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphViewAnimationHandler.swift; sourceTree = "<group>"; };
9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = "<group>"; };
948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = "<group>"; };
D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUINavigationController.h; sourceTree = "<group>"; };
@ -751,6 +755,8 @@
0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */,
0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */,
01004F2F22721C3800991ECC /* RadioButton.swift */,
943784F3236B77BB006A1E82 /* GraphView.swift */,
943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */,
);
path = Views;
sourceTree = "<group>";
@ -1038,6 +1044,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
943784F5236B77BB006A1E82 /* GraphView.swift in Sources */,
D29DF32121ED0CBA003B2FB9 /* LabelView.m in Sources */,
DBC4391822442197001AB423 /* CaretView.swift in Sources */,
D29770F221F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsTableViewController.m in Sources */,
@ -1124,6 +1131,7 @@
D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */,
D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */,
D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */,
943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */,
D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */,
948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */,
D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */,

View File

@ -0,0 +1,280 @@
//
// GraphView.swift
// MobileFirstFramework
//
// Created by Ryan on 10/24/19.
// Copyright © 2019 Verizon Wireless. All rights reserved.
//
import UIKit
enum GraphSize: String {
case small, medium, large
}
enum GraphStyle: String {
case unlimited, safetyMode
}
///Graph Object contains properties
public struct GraphObject {
var style: GraphStyle {
didSet {
updateStyle()
}
}
var size: GraphSize {
didSet {
updateSize()
}
}
var diameter: CGFloat = 24
var lineWidth: CGFloat = 5
var clockwise: Bool = true
var duration : Double = 1.0
var colors = [CGColor]()
public init(_ json: [AnyHashable : Any]?) {
style = .unlimited
size = .small
guard let json = json else {
return
}
if let styleString = json.optionalStringForKey("style") {
style = GraphStyle(rawValue: styleString) ?? .unlimited
}
if let sizeString = json.optionalStringForKey("size") {
size = GraphSize(rawValue: sizeString) ?? .small
}
updateStyle()
updateSize()
if let diameter = json.optionalCGFloatForKey("diameter") {
self.diameter = diameter
}
if let lineWidth = json.optionalCGFloatForKey("lineWidth") {
self.lineWidth = lineWidth
}
if let clockwise = json.optionalBoolForKey("clockwise") {
self.clockwise = clockwise
}
if let duration = json["duration"] as? Double {
self.duration = duration
}
if let colorArray = json.optionalArrayForKey("colors") as? [String] {
colors = getCGColorsFromArray(colorArray)
}
}
func getCGColorsFromArray(_ colorArray: [String]) -> [CGColor] {
return colorArray.map { (colorString) -> CGColor in
return UIColor.mfGet(forHex: colorString).cgColor
}
}
mutating func updateStyle() {
switch style {
case .unlimited:
duration = 1.0
clockwise = true
//current style, only the end part shows darker look
colors = getCGColorsFromArray(["#007AB8","#007AB8","#033554"])
break
case .safetyMode:
duration = 1.5
clockwise = true
colors = getCGColorsFromArray(["#CC4D0F","#CC4D0F","AB0309"])
break
}
}
//those are
mutating func updateSize() {
switch size {
case .small:
diameter = MFSizeObject(standardSize: 24)?.getValueBasedOnApplicationWidth() ?? 24
lineWidth = MFSizeObject(standardSize: 5)?.getValueBasedOnApplicationWidth() ?? 5
break
case .medium:
diameter = MFSizeObject(standardSize: 100)?.getValueBasedOnApplicationWidth() ?? 100
lineWidth = MFSizeObject(standardSize: 8)?.getValueBasedOnApplicationWidth() ?? 8
break
case .large:
diameter = MFSizeObject(standardSize: 180)?.getValueBasedOnApplicationWidth() ?? 180
lineWidth = MFSizeObject(standardSize: 12)?.getValueBasedOnApplicationWidth() ?? 12
break
}
}
}
@objcMembers open class GraphView: View {
var heightConstraint: NSLayoutConstraint?
var gradientLayer: CALayer?
var graphObject: GraphObject?
// MARK: setup
open override func setupView() {
super.setupView()
//avoid adding height constraint multiple times
guard heightConstraint == nil else { return }
heightConstraint = heightAnchor.constraint(equalToConstant: 0)
heightConstraint?.isActive = true
widthAnchor.constraint(equalTo: heightAnchor).isActive = true
}
override open func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) {
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
let object = GraphObject(json)
graphObject = object
createGraphCircle(object)
rotationAnimation(object)
}
class func getAngle(_ piValue: Double) -> Double {
return piValue / (2.0 * Double.pi) * 360.0
}
class func getPiValue(_ angle: Double) -> Double {
return angle / 360.0 * 2.0 * Double.pi
}
// MARK: circle
open func createGraphCircle(_ graphObject: GraphObject) {
if let sublayers = layer.sublayers {
for sublayer in sublayers {
sublayer.removeAllAnimations()
sublayer.removeFromSuperlayer()
}
}
heightConstraint?.constant = graphObject.diameter
//create circle path
let radius = graphObject.diameter / 2.0
//begin point will be at the bottom, clockwise direction
let path = UIBezierPath(arcCenter: CGPoint(x: radius
, y: radius), radius: radius - graphObject.lineWidth/2.0, startAngle: CGFloat(GraphView.getPiValue(90.0)), endAngle: CGFloat(GraphView.getPiValue(90.0 + 360.0)), clockwise: true)
path.lineWidth = graphObject.lineWidth
let circleLayer = CAShapeLayer()
circleLayer.path = path.cgPath
circleLayer.lineCap = .round
circleLayer.lineWidth = graphObject.lineWidth
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.strokeColor = UIColor.black.cgColor
//create gradient layer
let gradientLayer = createGradientLayer(graphObject)
gradientLayer.mask = circleLayer
layer.addSublayer(gradientLayer)
self.gradientLayer = gradientLayer
}
/*
create three gradient layer for circle layout.
_____________
| | top layer for smooth gradient
-------------
| | |
| | |
| | |
-------------
*/
func createGradientLayer(_ graphObject: GraphObject) -> CALayer {
let containLayer = CALayer()
containLayer.frame = CGRect(x: 0, y: 0, width: graphObject.diameter, height: graphObject.diameter)
let radius = graphObject.diameter / 2.0
//create graident layers
guard graphObject.colors.count > 1 else {
containLayer.backgroundColor = graphObject.colors.first
return containLayer
}
var topGradientHeight : CGFloat = 0.0
var leftColors = graphObject.colors.prefix(through: graphObject.colors.count/2)
let rightColors = graphObject.colors.suffix(from: graphObject.colors.count/2)
// make the top layer higher than line width for smooth look
topGradientHeight = min(max(graphObject.lineWidth, 1.0/(1.0+CGFloat(graphObject.colors.count))*graphObject.diameter), graphObject.diameter)
let topLayer = CAGradientLayer()
topLayer.frame = CGRect(x: 0.0, y: 0.0, width: graphObject.diameter, height: topGradientHeight)
//make the graident edge more smoothy
topLayer.startPoint = CGPoint(x: 0.25, y: 0.0)
topLayer.endPoint = CGPoint(x: 0.75, y: 0.0)
//if number of colors is even, need to display gradient layer, otherwise make top layer as solid color layer
if graphObject.colors.count % 2 == 0 {
leftColors.removeLast()
topLayer.colors = [leftColors.last!, rightColors.first!]
} else {
topLayer.backgroundColor = leftColors.last
}
containLayer.addSublayer(topLayer)
let leftLayer = CAGradientLayer()
leftLayer.frame = CGRect(x: 0, y: topGradientHeight, width: radius, height: graphObject.diameter - topGradientHeight)
leftLayer.startPoint = CGPoint(x: 0, y: 1)
leftLayer.endPoint = CGPoint(x: 0, y: 0)
//count of graidentLayer.colors must be bigger than 1, otherwise set backgroundColor
if leftColors.count > 1 {
leftLayer.colors = Array(leftColors)
} else {
leftLayer.backgroundColor = leftColors.first
}
containLayer.addSublayer(leftLayer)
let rightLayer = CAGradientLayer()
rightLayer.frame = CGRect(x: radius, y: topGradientHeight, width: radius, height: graphObject.diameter - topGradientHeight)
rightLayer.startPoint = CGPoint(x: 0, y: 0)
rightLayer.endPoint = CGPoint(x: 0, y: 1)
if rightColors.count > 1 {
rightLayer.colors = Array(rightColors)
} else {
rightLayer.backgroundColor = rightColors.first
}
containLayer.addSublayer(rightLayer)
return containLayer
}
//MARK: Animation
func rotationAnimation(_ object: GraphObject) {
MVMCoreDispatchUtility.performBlock(onMainThread:{
let rotation = CABasicAnimation(keyPath: "transform.rotation")
let animationHandler = GraphViewAnimationHandler.shared
let startAngle = animationHandler.getAnimationStartAngle(object.duration, CACurrentMediaTime())
if startAngle == 0.0 {
animationHandler.storeAnimation(object.duration, CACurrentMediaTime())
}
var fromValue = GraphView.getPiValue(0.0 + startAngle), toValue = GraphView.getPiValue(360.0 + startAngle)
if !object.clockwise {
fromValue = GraphView.getPiValue(360.0 - startAngle)
toValue = GraphView.getPiValue(0.0 - startAngle)
}
rotation.fromValue = fromValue
rotation.toValue = toValue
rotation.duration = object.duration
rotation.timingFunction = CAMediaTimingFunction(name: .linear)
rotation.fillMode = .both
rotation.isRemovedOnCompletion = false
//avoid infinity animation take high CPU momery usage when layer is not displayed
rotation.delegate = self
rotation.repeatCount = 1
self.gradientLayer?.add(rotation, forKey: "rotation")
})
}
}
extension GraphView: CAAnimationDelegate {
public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
if let object = graphObject {
rotationAnimation(object)
}
}
}

View File

@ -0,0 +1,32 @@
//
// GraphViewAnimationHandler.swift
// MobileFirstFramework
//
// Created by Ryan on 10/29/19.
// Copyright © 2019 Verizon Wireless. All rights reserved.
//
import UIKit
@objcMembers open class GraphViewAnimationHandler: NSObject {
/// duration : CACurrentMediaTime()
private var animations = [Double: Double]()
static let shared = GraphViewAnimationHandler()
open func storeAnimation(_ duration: Double, _ currentTime: CFTimeInterval) {
guard animations[duration] == nil else {
return
}
animations[duration] = currentTime
}
open func getAnimationStartAngle(_ duration: Double, _ currentTime: CFTimeInterval) -> Double {
if let time = animations[duration] {
return (currentTime - time) / duration * 360 + 90
}
return 0.0
}
}

View File

@ -42,6 +42,7 @@
@"checkboxWithLabelView" : CheckboxWithLabelView.class,
@"cornerLabels" : CornerLabels.class,
@"progressbar": ProgressBar.class,
@"circleProgress": GraphView.class,
@"multiProgressBar": MultiProgress.class,
@"checkbox": MVMCoreUICheckBox.class,
@"radioButton": RadioButton.class,