Weird gradient when using CAGradientLayer with radial gradient. - ios

Using code:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let v = View()
view.addSubview(v)
v.frame = CGRect(x: 100, y: 100, width: 200, height: 200)
}
}
class View : UIView {
let gradientLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .blue
gradientLayer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
gradientLayer.type = .radial
gradientLayer.colors = [
UIColor.red.cgColor,
UIColor.green.cgColor
]
self.layer.addSublayer(gradientLayer)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
expect(generated by Sketch):
code result on a iOS 12 simulator:
code result on a real iOS device:
My Xcode version is 10.0 (10A255), I found the problem occurs only when
startPoint.x == endPoint.x || startPoint.y == endPoint.y

When the y of startPoint and endPoint are equal, that means the height of the gradient ellipse will be 0.
In your code snippet, you can set entPoint's y to 1 to achieve the Sketch effect.
In the QuartzCore framework, there are the following comments:
/* Radial gradient. The gradient is defined as an ellipse with its
* center at 'startPoint' and its width and height defined by
* '(endPoint.x - startPoint.x) * 2' and '(endPoint.y - startPoint.y) *
* 2' respectively. */
#available(iOS 3.2, *)
public static let radial: CAGradientLayerType

Related

Draw rectangle with clear background on top of imageView

I am trying to draw a rectangle with a random border color with a clear background on top of an imageView. I need to loop through a set of co-ordinates and add appropriate rectangles to my image. I can't manage to get the rectangle background colour to be clear. Code below:
// the draw class
class Draw: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
let h = rect.height
let w = rect.width
let color:UIColor = Palette.getRandomColour()
//var drect = CGRect(x: (w * 0.25),y: (h * 0.25),width: (w * 0.5),height: (h * 0.5))
let drect = CGRect(x: rect.minX ,y: rect.minY , width: w, height: h)
let bpath:UIBezierPath = UIBezierPath(rect: drect)
UIColor.clear.setFill()
color.set()
bpath.stroke()
print("it ran")
NSLog("drawRect has updated the view")
}
}
// and in my viewDidLoad
for bp in sbp {
if let master = Bodyparts.getID(bp.masterid) {
let width = master.x2 - master.x1
let height = master.y2 - master.y1
let k = Draw(frame: CGRect(origin: CGPoint(x: master.x1, y: master.y1), size: CGSize(width: width, height: height)))
self.imgView.addSubview(k)
}
}
You need to set the background to clear. Here is a playground that you can copy and paste:
import PlaygroundSupport
import UIKit
class Draw: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
let h = rect.height
let w = rect.width
let color:UIColor = .red
let drect = CGRect(x: rect.minX ,y: rect.minY , width: w, height: h)
let bpath:UIBezierPath = UIBezierPath(rect: drect)
UIColor.clear.setFill()
color.set()
bpath.stroke()
}
}
let d = Draw(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
let view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
view.backgroundColor = .green
view.addSubview(d)
PlaygroundPage.current.liveView = view
I'm not exactly sure if mean a transparent center of the rect by clear but you can use the following method.
You would need to set the style of the Paint to stroke, to draw a box instead of a filled rect. Basically you will draw a bounding box if you use the Paint.Style.Stroke option of your Paint.
// Give the canvas an image to draw on
val canvas: Canvas = bitmap?.let { Canvas(it) }!!
// Test points for rect
val left = 100f
val right = 300f
val top = 100f
val bottom = 500f
// Paint definition for bounding box
var borderPaint: Paint = Paint()
borderPaint.strokeWidth = 10f
borderPaint.color = Color.GREEN
borderPaint.style = Paint.Style.STROKE
// Draw border
canvas.drawRect(left, top, right, bottom+100, borderPaint)
// Show the bitmap on display
viewBinding.imageView.setImageBitmap(bitmap)

Create CAGradient Layer with transparent hole in it?

Currently I do it like this:
final class BorderedButton: BottomNavigationButton {
private let gradient = CAGradientLayer()
private let shapeLayer = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
let radius = bounds.size.height / 2
let outside = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height), cornerRadius: radius)
let inside = UIBezierPath(roundedRect: CGRect(x: 3.0, y: 3.0, width: bounds.width - 6, height: bounds.height - 6), cornerRadius: radius - 3)
outside.append(inside)
outside.usesEvenOddFillRule = true
shapeLayer.path = outside.cgPath
}
init(color: UIColor?) {
super.init(frame: .zero)
let isGradient = color == nil
shapeLayer.fillRule = kCAFillRuleEvenOdd
shapeLayer.fillColor = UIColor.red.cgColor
layer.insertSublayer(shapeLayer, at: 0)
//gradient part
gradient.colors = isGradient ? [Constants.gradientStart, Constants.gradientEnd] : [color!.cgColor, color!.cgColor]
gradient.startPoint = CGPoint(x: 0.2, y: 0.5)
gradient.endPoint = CGPoint(x: 1, y: 1)
}
}
How can I apply that gradient to my code?
Don't add the CAShapeLayer to the view's layer, rather set it as the mask of the CAGradientLayer. Also don't forget to set the bounds of the gradient layer.
I had to make some modifications to get it to run in a playground, but this works for me:
let gradientStart = UIColor.orange
let gradientEnd = UIColor.blue
final class BorderedButton: UIButton {
private let gradient = CAGradientLayer()
private let shapeLayer = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
let radius = bounds.size.height / 2
let outside = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height), cornerRadius: radius)
let inside = UIBezierPath(roundedRect: CGRect(x: 3.0, y: 3.0, width: bounds.width - 6, height: bounds.height - 6), cornerRadius: radius - 3)
outside.append(inside)
outside.usesEvenOddFillRule = true
shapeLayer.path = outside.cgPath
gradient.frame = CGRect(x: 0, y: 0, width: bounds.size.width, height: bounds.size.height)
}
init(color: UIColor?, frame: CGRect = .zero) {
super.init(frame: frame)
let isGradient = color == nil
shapeLayer.fillRule = kCAFillRuleEvenOdd
//gradient part
gradient.colors = isGradient ? [gradientStart.cgColor, gradientEnd.cgColor] : [color!.cgColor, color!.cgColor]
gradient.startPoint = CGPoint(x: 0.2, y: 0.5)
gradient.endPoint = CGPoint(x: 1, y: 1)
gradient.mask = shapeLayer
layer.addSublayer(gradient)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

I took a github project where he added a button programatically, but I added it with the interface builder but it doesn't work

Can Someone help me with that github project how to implement in my project with some code
This code is the one where I created the button using IB builder and created an outlet for the button and changed its class to a custom class and added the toggle function and the .TouchUpInside line
And I get an error
fatal error: unexpectedly found nil while unwrapping an Optional value
2017-07-03 00:51:21.630176+0530 ala[867:183071] fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
when I tap on the button and in its default state its not even visible
import UIKit
class serchViewController: UIViewController {
#IBOutlet weak var menuView: UIView!
#IBOutlet weak var menuBtn: HamburgerButton!
override func viewDidLoad() {
super.viewDidLoad()
let screenSize: CGRect = UIScreen.main.bounds
menuView.frame = CGRect (x: 0, y: 0, width: screenSize.width/4, height: screenSize.height)
self.menuBtn.addTarget(self, action: #selector(serchViewController.toggle(_:)), for: .touchUpInside)
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func toggle(_ sender: AnyObject!) {
self.menuBtn.showsMenu = !self.menuBtn.showsMenu
}
}
This is the original code
import UIKit
class ViewController: UIViewController {
var button: HamburgerButton! = nil
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor(red: 38.0 / 255, green: 151.0 / 255, blue: 68.0 / 255, alpha: 1)
self.button = HamburgerButton(frame: CGRect(x: 133, y: 133, width: 54, height: 54))
self.button.addTarget(self, action: #selector(ViewController.toggle(_:)), for:.touchUpInside)
self.view.addSubview(button)
}
override var preferredStatusBarStyle : UIStatusBarStyle {
return .lightContent
}
func toggle(_ sender: AnyObject!) {
self.button.showsMenu = !self.button.showsMenu
}
}
the error file code is below
import CoreGraphics
import QuartzCore
import UIKit
class HamburgerButton : UIButton {
let shortStroke: CGPath = {
let path = CGMutablePath()
path.move(to: CGPoint(x: 2, y: 2))
path.addLine(to: CGPoint(x: 28, y:2))
return path
}()
let outline: CGPath = {
let path = CGMutablePath()
path.move(to: CGPoint(x: 10, y: 27))
path.addCurve(to: CGPoint(x: 40, y: 27), control1: CGPoint(x: 12, y: 27), control2: CGPoint(x: 28.02, y: 27))
path.addCurve(to: CGPoint(x: 27, y: 02), control1: CGPoint(x: 55.92, y: 27), control2: CGPoint(x: 50.47, y: 2))
path.addCurve(to: CGPoint(x: 2, y: 27), control1: CGPoint(x: 13.16, y: 2), control2: CGPoint(x: 2, y: 13.16))
path.addCurve(to: CGPoint(x: 27, y: 52), control1: CGPoint(x: 2, y: 40.84), control2: CGPoint(x: 13.16, y: 52))
path.addCurve(to: CGPoint(x: 52, y: 27), control1: CGPoint(x: 40.84, y: 52), control2: CGPoint(x: 52, y: 40.84))
path.addCurve(to: CGPoint(x: 27, y: 2), control1: CGPoint(x: 52, y: 13.16), control2: CGPoint(x: 42.39, y: 2))
path.addCurve(to: CGPoint(x: 2, y: 27), control1: CGPoint(x: 13.16, y: 2), control2: CGPoint(x: 2, y: 13.16))
return path
}()
let menuStrokeStart: CGFloat = 0.325
let menuStrokeEnd: CGFloat = 0.9
let hamburgerStrokeStart: CGFloat = 0.028
let hamburgerStrokeEnd: CGFloat = 0.111
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
self.top.path = shortStroke
self.middle.path = outline
self.bottom.path = shortStroke
for layer in [ self.top, self.middle, self.bottom ] {
layer?.fillColor = nil
layer?.strokeColor = UIColor.blue.cgColor
layer?.lineWidth = 4
layer?.miterLimit = 4
layer?.lineCap = kCALineCapRound
layer?.masksToBounds = true
let strokingPath = CGPath(__byStroking: (layer?.path!)!, transform: nil, lineWidth: 4, lineCap: .round, lineJoin: .miter, miterLimit: 4)
layer?.bounds = (strokingPath?.boundingBoxOfPath)!
layer?.actions = [
"strokeStart": NSNull(),
"strokeEnd": NSNull(),
"transform": NSNull()
]
self.layer.addSublayer(layer!)
}
self.top.anchorPoint = CGPoint(x: 28.0 / 30.0, y: 0.5)
self.top.position = CGPoint(x: 40, y: 18)
self.middle.position = CGPoint(x: 27, y: 27)
self.middle.strokeStart = hamburgerStrokeStart
self.middle.strokeEnd = hamburgerStrokeEnd
self.bottom.anchorPoint = CGPoint(x: 28.0 / 30.0, y: 0.5)
self.bottom.position = CGPoint(x: 40, y: 36)
}
var showsMenu: Bool = false {
didSet {
let strokeStart = CABasicAnimation(keyPath: "strokeStart")
let strokeEnd = CABasicAnimation(keyPath: "strokeEnd")
if self.showsMenu {
strokeStart.toValue = menuStrokeStart
strokeStart.duration = 0.5
strokeStart.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, -0.4, 0.5, 1)
strokeEnd.toValue = menuStrokeEnd
strokeEnd.duration = 0.6
strokeEnd.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, -0.4, 0.5, 1)
} else {
strokeStart.toValue = hamburgerStrokeStart
strokeStart.duration = 0.5
strokeStart.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, 0, 0.5, 1.2)
strokeStart.beginTime = CACurrentMediaTime() + 0.1
strokeStart.fillMode = kCAFillModeBackwards
strokeEnd.toValue = hamburgerStrokeEnd
strokeEnd.duration = 0.6
strokeEnd.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, 0.3, 0.5, 0.9)
}
self.middle.ocb_applyAnimation(strokeStart)
self.middle.ocb_applyAnimation(strokeEnd)
let topTransform = CABasicAnimation(keyPath: "transform")
topTransform.timingFunction = CAMediaTimingFunction(controlPoints: 0.5, -0.8, 0.5, 1.85)
topTransform.duration = 0.4
topTransform.fillMode = kCAFillModeBackwards
let bottomTransform = topTransform.copy() as! CABasicAnimation
if self.showsMenu {
let translation = CATransform3DMakeTranslation(-4, 0, 0)
topTransform.toValue = NSValue(caTransform3D: CATransform3DRotate(translation, -0.7853975, 0, 0, 1))
topTransform.beginTime = CACurrentMediaTime() + 0.25
bottomTransform.toValue = NSValue(caTransform3D: CATransform3DRotate(translation, 0.7853975, 0, 0, 1))
bottomTransform.beginTime = CACurrentMediaTime() + 0.25
} else {
topTransform.toValue = NSValue(caTransform3D: CATransform3DIdentity)
topTransform.beginTime = CACurrentMediaTime() + 0.05
bottomTransform.toValue = NSValue(caTransform3D: CATransform3DIdentity)
bottomTransform.beginTime = CACurrentMediaTime() + 0.05
}
self.top.ocb_applyAnimation(topTransform)
self.bottom.ocb_applyAnimation(bottomTransform)
}
}
var top: CAShapeLayer! = CAShapeLayer()
var bottom: CAShapeLayer! = CAShapeLayer()
var middle: CAShapeLayer! = CAShapeLayer()
}
extension CALayer {
func ocb_applyAnimation(_ animation: CABasicAnimation) {
let copy = animation.copy() as! CABasicAnimation
if copy.fromValue == nil {
copy.fromValue = self.presentation()!.value(forKeyPath: copy.keyPath!)
}
self.add(copy, forKey: copy.keyPath)
self.setValue(copy.toValue, forKeyPath:copy.keyPath!)
}
}
Project GitHub link
error screenshot
IB builder screenshot
There is an open pull request on the linked project to make it usable from interface builder. Why not try using that fork instead of the original repo?
The reason it isn't working currently is that a lot of setup is being done in the init(frame:) method. That method doesn't get called when you create an object from the storyboard, init(coder:) is used instead. In the code above that method is empty, so none of the setup is done.
Normally you would put common setup code in a separate method and call it from both initialisers. That isn't the approach the PR author has taken, but you can always make your own fork.
As an example:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
self.top.path = shortStroke
self.middle.path = outline
self.bottom.path = shortStroke
// plus all the other code in here...
}
Would become:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonSetUp()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.commonSetUp()
}
private func commonSetUp() {
self.top.path = shortStroke
self.middle.path = outline
self.bottom.path = shortStroke
// plus all the other code in here...
}
Can you try changing #selector(serchViewController.toggle(_:)) to #selector(toggle(_:)). Looks like it's finding nil since you're trying to point it to a class func. You've said in the method that your target is self it should be pointing to a method in the current instance.

Seeing the draw using IBDesignable+UIView(storyboard) but can't see on app

I'm trying to draw some dashed lines on an app but it only draws on main.storyboard with IBDesignable. When I run the app on iOS simulator, nothing shows. What's happening?
The code to draw:
#IBDesignable
class AnalogView: UIView {
fileprivate let thickHorizontalLayer = CAShapeLayer()
fileprivate let thinHorizontalLayer = CAShapeLayer()
#IBInspectable var thickYCoord = 50.0
#IBInspectable var thinYCoord = 52.5
override init(frame: CGRect) {
super.init(frame: frame)
let thickDashesPath = UIBezierPath()
thickDashesPath.move(to: CGPoint(x: 0, y: thickYCoord)) //left
thickDashesPath.addLine(to: CGPoint(x: 340, y: thickYCoord)) //right
//thickHorizontalLayer.frame = frame
thickHorizontalLayer.path = thickDashesPath.cgPath
thickHorizontalLayer.strokeColor = UIColor.black.cgColor //dashes color
thickHorizontalLayer.lineWidth = 20
thickHorizontalLayer.lineDashPattern = [ 1, 83.5 ]
//thickHorizontalLayer.lineDashPhase = 0.25
self.layer.addSublayer(thickHorizontalLayer)
let thinDashesPath = UIBezierPath()
thinDashesPath.move(to: CGPoint(x: 0, y: thinYCoord)) //esquerda
thinDashesPath.addLine(to: CGPoint(x: 340, y: thinYCoord)) //direita
//thinHorizontalLayer.frame = frame
thinHorizontalLayer.path = thinDashesPath.cgPath
thinHorizontalLayer.strokeColor = UIColor.black.cgColor
thinHorizontalLayer.lineWidth = 15.0
thinHorizontalLayer.fillColor = UIColor.clear.cgColor
thinHorizontalLayer.lineDashPattern = [ 0.5, 7.95]
//thinHorizontalLayer.lineDashPhase = 0.25
self.layer.addSublayer(thinHorizontalLayer)
You need to put the common code in a routine that is called by both init(frame:) and init(coder:):
#IBDesignable
class AnalogView: UIView {
fileprivate let thickHorizontalLayer = CAShapeLayer()
fileprivate let thinHorizontalLayer = CAShapeLayer()
#IBInspectable var thickYCoord: CGFloat = 50.0
#IBInspectable var thinYCoord: CGFloat = 52.5
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
private func configure() {
let thickDashesPath = UIBezierPath()
thickDashesPath.move(to: CGPoint(x: 0, y: thickYCoord)) //left
thickDashesPath.addLine(to: CGPoint(x: 340, y: thickYCoord)) //right
thickHorizontalLayer.path = thickDashesPath.cgPath
thickHorizontalLayer.strokeColor = UIColor.black.cgColor //dashes color
thickHorizontalLayer.lineWidth = 20
thickHorizontalLayer.lineDashPattern = [ 1, 83.5 ]
self.layer.addSublayer(thickHorizontalLayer)
let thinDashesPath = UIBezierPath()
thinDashesPath.move(to: CGPoint(x: 0, y: thinYCoord)) //esquerda
thinDashesPath.addLine(to: CGPoint(x: 340, y: thinYCoord)) //direita
thinHorizontalLayer.path = thinDashesPath.cgPath
thinHorizontalLayer.strokeColor = UIColor.black.cgColor
thinHorizontalLayer.lineWidth = 15.0
thinHorizontalLayer.fillColor = UIColor.clear.cgColor
thinHorizontalLayer.lineDashPattern = [ 0.5, 7.95]
self.layer.addSublayer(thinHorizontalLayer)
}
}
I'd also suggest declaring an explicit type for your #IBInspectable types, or else you won't be able to adjust them in IB.
Personally, rather than hard coding the path width, I'd update it when the layout changes. Also, if you're going to make those properties #IBDesignable, you really want to update the paths if they change.
#IBDesignable
class AnalogView: UIView {
fileprivate let thickHorizontalLayer = CAShapeLayer()
fileprivate let thinHorizontalLayer = CAShapeLayer()
#IBInspectable var thickYCoord: CGFloat = 50.0 { didSet { updatePaths() } }
#IBInspectable var thinYCoord: CGFloat = 52.5 { didSet { updatePaths() } }
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
private func configure() {
layer.addSublayer(thickHorizontalLayer)
layer.addSublayer(thinHorizontalLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
updatePaths()
}
private func updatePaths() {
let thickDashesPath = UIBezierPath()
thickDashesPath.move(to: CGPoint(x: bounds.origin.x, y: thickYCoord)) //left
thickDashesPath.addLine(to: CGPoint(x: bounds.origin.x + bounds.size.width, y: thickYCoord)) //right
thickHorizontalLayer.path = thickDashesPath.cgPath
thickHorizontalLayer.strokeColor = UIColor.black.cgColor //dashes color
thickHorizontalLayer.lineWidth = 20
thickHorizontalLayer.lineDashPattern = [1.0, NSNumber(value: Double(bounds.size.width - 1) / 4 - 1.0) ]
let thinDashesPath = UIBezierPath()
thinDashesPath.move(to: CGPoint(x: bounds.origin.x, y: thinYCoord)) //esquerda
thinDashesPath.addLine(to: CGPoint(x: bounds.origin.x + bounds.size.width, y: thinYCoord)) //direita
thinHorizontalLayer.path = thinDashesPath.cgPath
thinHorizontalLayer.strokeColor = UIColor.black.cgColor
thinHorizontalLayer.lineWidth = 15.0
thinHorizontalLayer.fillColor = UIColor.clear.cgColor
thinHorizontalLayer.lineDashPattern = [0.5, NSNumber(value: Double(bounds.size.width - 1) / 40 - 0.5)]
}
}
You might want to adjust the dashing to span the width, too (I'm not sure if you wanted a consistent scale or for it to span the width). But hopefully this illustrates the idea.

iOS Swift Rectangle

I am trying to add a rectangular shape (curve as shown in picture) to my existing UIView.
.
This is the code I have implemented:
func drawRect(rect: CGRect) {
let y:CGFloat = 20
let curveTo:CGFloat = 0
let myBezier = UIBezierPath()
myBezier.move(to: CGPoint(x: 0, y: y))
myBezier.addQuadCurve(to: CGPoint(x: rect.width, y: y), controlPoint: CGPoint(x: rect.width / 2, y: curveTo))
myBezier.addLine(to: CGPoint(x: rect.width, y: rect.height))
myBezier.addLine(to: CGPoint(x: 0, y: rect.height))
myBezier.close()
let context = UIGraphicsGetCurrentContext()
context!.setLineWidth(4.0)
UIColor.yellow.setFill()
myBezier.fill()
}
Just to try and get a rectangle path to appear, however, when I open it in the simulator there is nothing. I believe it is because my other elements are overlayed on top of it is this correct? My full code is here
Thanks for your help.
Looking at your code. The UIViewController don't have overridden message drawRect. You should create custom class derived from UIView and override message drawRect there.
This could be made much more flexible, but it might suit your needs. If it doesn't, it could be a good starting point for you to get to what you want.
If you haven't looked at custom components / subclassing UIView / using IBInspectable and IBDesignable, this probably won't make much sense, so you might have some reading to do :)
//
// RoundedBottomImageView.swift
// SW3IBDesign
//
// Created by Don Mag on 2/27/17.
// Copyright © 2017 DonMag. All rights reserved.
//
import UIKit
#IBDesignable
class RoundedBottomImageView: UIView {
var imageView: UIImageView!
#IBInspectable var image: UIImage? {
didSet { self.imageView.image = image }
}
#IBInspectable var roundingValue: CGFloat = 0.0 {
didSet {
self.setNeedsLayout()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
doMyInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
doMyInit()
}
func doMyInit() {
imageView = UIImageView()
imageView.backgroundColor = UIColor.red
imageView.contentMode = UIViewContentMode.scaleToFill
addSubview(imageView)
}
override func layoutSubviews() {
super.layoutSubviews()
imageView.frame = self.bounds
let rect = self.bounds
let y:CGFloat = rect.size.height - roundingValue
let curveTo:CGFloat = rect.size.height
let myBezier = UIBezierPath()
myBezier.move(to: CGPoint(x: 0, y: y))
myBezier.addQuadCurve(to: CGPoint(x: rect.width, y: y), controlPoint: CGPoint(x: rect.width / 2, y: curveTo))
myBezier.addLine(to: CGPoint(x: rect.width, y: 0))
myBezier.addLine(to: CGPoint(x: 0, y: 0))
myBezier.close()
let maskForPath = CAShapeLayer()
maskForPath.path = myBezier.cgPath
layer.mask = maskForPath
}
}
Example app: https://github.com/DonMag/IBDesignInspect
Edit: Example now includes buttons to demonstrate changing the image via code.
Edit2: Example app now has an Alternate Storyboard to show how the image can be "path clipped" without using a custom subclass.

Resources