Skip to content

Commit

Permalink
Merge pull request #519 from phimage/feature/masks
Browse files Browse the repository at this point in the history
Add new mask types .drop, .plusSign and .moon Close #482
  • Loading branch information
phimage committed Oct 24, 2017
2 parents ca5c78d + 22a8b74 commit 5f0620d
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 37 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ N/A

#### Enhancements

- Add new mask types `.drop`, `.plusSign`, `.moon`. [#519](https://github.com/IBAnimatable/IBAnimatable/pull/519) by [@phimage](https://github.com/phimage)
- Add new mask types `.heart`, `.gear`, `.ring`, `.superEllipse`. [#518](https://github.com/IBAnimatable/IBAnimatable/pull/518) by [@phimage](https://github.com/phimage)

#### Bugfixes
Expand Down
8 changes: 8 additions & 0 deletions Documentation/APIs.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,19 @@ To use `IBAnimatable`, we can drag and drop a UIKit element and connect it with
**Supported MaskType:**

* `circle`
* `ellipse`
* `polygon`: Can also specify the number of sides of the polygon, e.g. use `Polygon(6)` to have a polygon with 6 sides. If not specified, the default is 6 sides.
* `triangle`
* `star`: Can also specify the points of the Star, e.g. use `Star(6)` to have a star with 6 points. If not specified, the default is 5 points.
* `wave`: Can use parameters to customize the `Wave`. `Wave(down, 20, 5)` means the Wave faces down, width is 20 and offset is 5. If not specified, the default value is `Wave(up, 40, 0)`.
* `parallelogram`: Can use parameters to customize the `Parallelogram`. `Parallelogram(60)` means the top left angle of the Parallelogram will have an angle of 60 degrees. The default value is `Parallelogram(60)`. if angle == 90 it will be a rectangular Mask. if angle < 90 the parallelogram will be oriented to left.
* `heart`
* `ring`: Can also specify the radius of the Ring, e.g. use `Ring(6)` to have a ring with 6 as radius.
* `gear`: Can also specify the radius of the Gear and the number of cogs , e.g. use `Gear(6, 8)` to have a gear with 6 as radius and 8 cogs.
* `superellipse`: Can also specify the curve of [Superellipse](https://en.wikipedia.org/wiki/Superellipse), e.g use `superellipse(1)` to have a diamond.
* `drop`
* `plussign`: Can also specify the thickness of the plus sign, e.g. use `Plussign(10)` to have a plug sign with 10 as thickness.
* `moon`: Can also specify the angle to change the Moon shape.
* `custom`: Allows you to use your own bezier path as mask. Only usable from code (not from IB). You have to pass in parameter a closure that takes a `CGSize` (the current's view Size) and returns the `UIBezierPath`


Expand Down
2 changes: 1 addition & 1 deletion IBAnimatableApp/IBAnimatableApp/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]?) -> Bool {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ final class AnimationsTimingFunctionViewController: UIViewController {
}
}

extension AnimationsTimingFunctionViewController : UIPickerViewDelegate, UIPickerViewDataSource {
extension AnimationsTimingFunctionViewController: UIPickerViewDelegate, UIPickerViewDataSource {

func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ private let directionParam = ParamType(fromEnum: AnimationType.Direction.self)
private let fadeWayParams = ParamType(fromEnum: AnimationType.FadeWay.self)
private let axisParams = ParamType(fromEnum: AnimationType.Axis.self)
private let rotationDirectionParams = ParamType(fromEnum: AnimationType.RotationDirection.self)
private let positiveNumberParam = ParamType.number(min: 0, max: 50, interval: 2, ascending: true, unit:"")
private let positiveNumberParam = ParamType.number(min: 0, max: 50, interval: 2, ascending: true, unit: "")
private let numberParam = ParamType.number(min: -50, max: 200, interval: 10, ascending: true, unit: "")
private let repeatCountParam = ParamType.number(min: 1, max: 10, interval: 1, ascending: true, unit:"")
private let repeatCountParam = ParamType.number(min: 1, max: 10, interval: 1, ascending: true, unit: "")
private let scaleParam = ParamType.number(min: 0, max: 2, interval: 0.1, ascending: true, unit: "")

final class AnimationsViewController: UIViewController {
Expand Down Expand Up @@ -78,7 +78,7 @@ extension AnimationsViewController: TimingFunctionPickDelegate {

}

extension AnimationsViewController : UIPickerViewDelegate, UIPickerViewDataSource {
extension AnimationsViewController: UIPickerViewDelegate, UIPickerViewDataSource {

func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ final class PresentingViewController: AnimatableViewController, UIPickerViewData
presentedViewController.dismissalAnimationType = PresentationAnimationType(string: selectedDismissalAnimationType) ?? .cover(from: .bottom)
presentedViewController.modalPosition = PresentationModalPosition(string: selectedModalPosition)
presentedViewController.modalSize = (width: PresentationModalSize(string: selectedModalWidth)!,
height: PresentationModalSize(string:selectedModalHeight)!)
height: PresentationModalSize(string: selectedModalHeight)!)

presentedViewController.backgroundColor = colors[Int(sliderBackgroundColor.value)]
presentedViewController.opacity = CGFloat(sliderOpacity.value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class BlurEffectViewController: UIViewController {
}()
}

extension BlurEffectViewController : UIPickerViewDelegate, UIPickerViewDataSource {
extension BlurEffectViewController: UIPickerViewDelegate, UIPickerViewDataSource {
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 2 {
return opacityValues.count()
Expand All @@ -41,8 +41,8 @@ extension BlurEffectViewController : UIPickerViewDelegate, UIPickerViewDataSourc
}

func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
blurEffectView.blurEffectStyle = UIBlurEffectStyle(string:values[pickerView.selectedRow(inComponent: 0)])
blurEffectView.vibrancyEffectStyle = UIBlurEffectStyle(string:values[pickerView.selectedRow(inComponent: 1)])
blurEffectView.blurEffectStyle = UIBlurEffectStyle(string: values[pickerView.selectedRow(inComponent: 0)])
blurEffectView.vibrancyEffectStyle = UIBlurEffectStyle(string: values[pickerView.selectedRow(inComponent: 1)])
blurEffectView.blurOpacity = CGFloat(Double(opacityValues.value(at: pickerView.selectedRow(inComponent: 2)))!)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ final class GradientCustomStartPointViewController: UIViewController {
}
}

extension GradientCustomStartPointViewController : UIPickerViewDelegate, UIPickerViewDataSource {
extension GradientCustomStartPointViewController: UIPickerViewDelegate, UIPickerViewDataSource {
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return componentValues[component].count()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ final class GradientViewController: UIViewController {

}

extension GradientViewController : UIPickerViewDelegate, UIPickerViewDataSource {
extension GradientViewController: UIPickerViewDelegate, UIPickerViewDataSource {
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return componentValues[component].count()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,29 @@ final class MaskViewController: UIViewController {
lazy var entries: [PickerEntry] = {
let pointsParam = ParamType.number(min: 3, max: 10, interval: 1, ascending: true, unit: "points") // default 5
let sidesParam = ParamType.number(min: 3, max: 10, interval: 1, ascending: true, unit: "sides") // default 6
let angleParam = ParamType.number(min: 60, max: 120, interval: 2, ascending: true, unit: "°") // default 6
let angleParam = ParamType.number(min: 60, max: 180, interval: 2, ascending: true, unit: "°") // default 6
let waveParam = ParamType(fromEnum: MaskType.WaveDirection.self)
let widthParam = ParamType.number(min: 15, max: 90, interval: 2, ascending: true, unit: "px")
let radiusParam = ParamType.number(min: 10, max: 40, interval: 10, ascending: true, unit: "px")
let cogsParam = ParamType.number(min: 3, max: 10, interval: 1, ascending: true, unit: "cogs")
let nParam = ParamType.number(min: 0.25, max: 2, interval: 0.25, ascending: true, unit: "n")

return [PickerEntry(params:[], name:"circle"),
PickerEntry(params:[], name:"ellipse"),
PickerEntry(params:[], name: "triangle"),
PickerEntry(params:[sidesParam], name: "polygon"),
PickerEntry(params:[pointsParam], name: "star"),
PickerEntry(params:[waveParam, widthParam], name: "wave"),
PickerEntry(params:[angleParam], name: "parallelogram"),
PickerEntry(params:[], name: "heart"),
PickerEntry(params:[radiusParam], name: "ring"),
PickerEntry(params:[radiusParam, cogsParam], name: "gear"),
PickerEntry(params:[nParam], name: "superellipse"),
PickerEntry(params:[], name: "none"),
PickerEntry(params:[], name: "CUSTOM Bubble")
return [PickerEntry(params: [], name: "circle"),
PickerEntry(params: [], name: "ellipse"),
PickerEntry(params: [], name: "triangle"),
PickerEntry(params: [sidesParam], name: "polygon"),
PickerEntry(params: [pointsParam], name: "star"),
PickerEntry(params: [waveParam, widthParam], name: "wave"),
PickerEntry(params: [angleParam], name: "parallelogram"),
PickerEntry(params: [], name: "heart"),
PickerEntry(params: [radiusParam], name: "ring"),
PickerEntry(params: [radiusParam, cogsParam], name: "gear"),
PickerEntry(params: [nParam], name: "superellipse"),
PickerEntry(params: [], name: "drop"),
PickerEntry(params: [widthParam], name: "plussign"),
PickerEntry(params: [angleParam], name: "moon"),
PickerEntry(params: [], name: "none"),
PickerEntry(params: [], name: "CUSTOM Bubble")
]
}()

Expand All @@ -43,13 +46,13 @@ final class MaskViewController: UIViewController {
selectedEntry = entries[0]
pickerView.delegate = self
pickerView.dataSource = self
let maskString = selectedEntry.toString(selectedIndexes:0)
let maskString = selectedEntry.toString(selectedIndexes: 0)
let maskType = MaskType(string: maskString)
maskedView.maskType = maskType
}
}

extension MaskViewController : UIPickerViewDelegate, UIPickerViewDataSource {
extension MaskViewController: UIPickerViewDelegate, UIPickerViewDataSource {

func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
Expand Down
18 changes: 18 additions & 0 deletions Sources/Enums/MaskType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public enum MaskType: IBEnum {
case gear(radius: Double, cogs: Int)
/// For super ellipse shape.
case superEllipse(n: Double)
/// For drop shape.
case drop
/// For plus sign shape
case plusSign(width: Double)
/// For moon shape
case moon(angle: Double)

/// Custom shape
case custom(pathProvider: CustomMaskProvider)
Expand Down Expand Up @@ -85,6 +91,12 @@ public extension MaskType {
self = .gear(radius: params[safe: 0]?.toDouble() ?? 10, cogs: params[safe: 1]?.toInt() ?? 6 )
case "superellipse":
self = .superEllipse(n: params[safe: 0]?.toDouble() ?? M_E )
case "drop":
self = .drop
case "plussign":
self = .plusSign(width: params[safe: 0]?.toDouble() ?? 10 )
case "moon":
self = .moon(angle: params[safe: 0]?.toDouble() ?? 60 )
default:
self = .none
}
Expand Down Expand Up @@ -117,6 +129,12 @@ extension MaskType {
return UIBezierPath(gearIn: rect, radius: CGFloat(radius), cogs: cogs)
case .superEllipse(let n):
return UIBezierPath(superEllipseInRect: rect, n: CGFloat(n))
case .drop:
return UIBezierPath(dropInRect: rect)
case .plusSign(let width):
return UIBezierPath(plusSignInRect: rect, width: CGFloat(width))
case .moon(let angle):
return UIBezierPath(moonInRect: rect, with: CGFloat(angle))
case let .custom(pathProvider):
return pathProvider(rect.size)
case .none:
Expand Down
132 changes: 122 additions & 10 deletions Sources/Extensions/UIBezierPathExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,7 @@ extension UIBezierPath {
*/
convenience init(heartIn bounds: CGRect) {
self.init()
var x: CGFloat = bounds.origin.x
var y: CGFloat = bounds.origin.y

// square bounds
let width = ceil(min(bounds.size.width, bounds.size.height))
let height = width
x += (bounds.size.width - width) / 2
y += (bounds.size.height - height) / 2
let (x, y, width, height) = bounds.centeredSquare.flatten()

let lowerPoint = CGPoint(x: x + width / 2, y: (y + height ))
move(to: lowerPoint)
Expand Down Expand Up @@ -220,7 +213,7 @@ extension UIBezierPath {

addArc(withCenter: .zero, radius: outerRadius, startAngle: 0, endAngle: .pi * 2, clockwise: true)

apply(CGAffineTransform(translationX: center.x, y: center.y))
self.translate(to: center)
usesEvenOddFillRule = true
}

Expand Down Expand Up @@ -253,7 +246,7 @@ extension UIBezierPath {
swap(&radius.0, &radius.1)
}

apply(CGAffineTransform(translationX: center.x, y: center.y))
self.translate(to: center)
}

/**
Expand Down Expand Up @@ -290,12 +283,119 @@ extension UIBezierPath {
close()
}

/**
Create a Bezier path for a drop shape.
- Parameter bounds: The bounds of shape.
*/
convenience init(dropInRect bounds: CGRect) {
self.init()
let (x, y, width, height) = bounds.centeredSquare.flatten()

let topPoint = CGPoint(x: x + width / 2, y: 0)
move(to: topPoint)

addCurve(to: CGPoint(x: x + width / 8, y: (y + (height * 5 / 8))),
controlPoint1: CGPoint(x: x + width / 2, y: height / 8),
controlPoint2: CGPoint(x: x + width / 8, y: (y + (height * 3 / 8))))

addArc(withCenter: CGPoint(x: (x + (width / 2)), y: (y + (height * 5 / 8))),
radius: (width * 3 / 8),
startAngle: .pi,
endAngle: 0,
clockwise: false)

addCurve(to: topPoint,
controlPoint1: CGPoint(x: x + width * 7 / 8, y: (y + (height * 3 / 8))),
controlPoint2: CGPoint(x: x + width / 2, y: height / 8))
}

/**
Create a Bezier path for a plus sign shape.
- Parameter bounds: The bounds of shape.
*/
convenience init(plusSignInRect bounds: CGRect, width signWidth: CGFloat) {
self.init()

let (x, y, width, height) = bounds/*.centeredSquare*/.flatten()
if signWidth > width {
return
}
let midX = x + width / 2
let midY = y + height / 2
let right = x + width
let left = x
let top = y
let bottom = y + height

move(to: CGPoint(x: midX - signWidth / 2, y: top))
addLine(to: CGPoint(x: midX + signWidth / 2, y: top))
addLine(to: CGPoint(x: midX + signWidth / 2, y: midY - signWidth / 2))
addLine(to: CGPoint(x: right, y: midY - signWidth / 2))
addLine(to: CGPoint(x: right, y: midY + signWidth / 2))
addLine(to: CGPoint(x: midX + signWidth / 2, y: midY + signWidth / 2))
addLine(to: CGPoint(x: midX + signWidth / 2, y: bottom))
addLine(to: CGPoint(x: midX - signWidth / 2, y: bottom))
addLine(to: CGPoint(x: midX - signWidth / 2, y: midY + signWidth / 2))
addLine(to: CGPoint(x: left, y: midY + signWidth / 2))
addLine(to: CGPoint(x: left, y: midY - signWidth / 2))
addLine(to: CGPoint(x: midX - signWidth / 2, y: midY - signWidth / 2))
addLine(to: CGPoint(x: midX - signWidth / 2, y: top))
}

/**
Create a Bezier path for a moon shape.
- Parameter bounds: The bounds of shape.
- Parameter angle: The angle.
*/
convenience init(moonInRect bounds: CGRect, with angle: CGFloat) {
self.init()
let radius = ceil(min(bounds.width, bounds.height) / 2)
let radian: CGFloat
if angle > 0 && angle < 180 {
radian = -angle * .pi / 180
} else {
radian = -90 * .pi / 180
}

addArc(withCenter: .zero, radius: radius, startAngle: -radian / 2, endAngle: radian / 2, clockwise: true)

if angle > 0 && angle < 180 {
addArc(withCenter: CGPoint(x: radius * cos(radian / 2.0), y: 0.0),
radius: radius * sin(radian/2.0), startAngle: CGFloat.pi / 2, endAngle: -CGFloat.pi / 2.0, clockwise: false)
} else {
addLine(to: .zero)
}

close()
self.translate(to: bounds.center)
}
}

private extension UIBezierPath {
func point(from angle: CGFloat, radius: CGFloat, offset: CGPoint) -> CGPoint {
return CGPoint(x: radius * cos(angle) + offset.x, y: radius * sin(angle) + offset.y)
}
func translate(tx: CGFloat, ty: CGFloat) {
apply(CGAffineTransform(translationX: tx, y: ty))
}
func translate(to point: CGPoint) {
apply(CGAffineTransform(translationX: point.x, y: point.y))
}
func rotate(with theta: CGFloat, around origine: CGPoint = .zero) {
guard theta != 0 else {
return
}
if origine != .zero {
translate(to: CGPoint(x: -origine.x, y: -origine.y))
}
apply(CGAffineTransform(rotationAngle: theta))
if origine != .zero {
translate(to: origine)
}
}
}

extension CGFloat {
Expand Down Expand Up @@ -325,4 +425,16 @@ private extension CGRect {
let outerRadius = diameter / 2
return (innerRadius, outerRadius)
}
var centeredSquare: CGRect {
let width = ceil(min(size.width, size.height))
let height = width

let newOrigin = CGPoint(x: origin.x + (size.width - width) / 2, y: origin.y + (size.height - height) / 2)
let newSize = CGSize(width: width, height: height)
return CGRect(origin: newOrigin, size: newSize)
}
// swiftlint:disable:next large_tuple
func flatten() -> (CGFloat, CGFloat, CGFloat, CGFloat) {
return (origin.x, origin.y, size.width, size.height)
}
}

0 comments on commit 5f0620d

Please sign in to comment.