i want to add curve in my UIView as shown in image.
How can i create such uiview?
You can use UIBezierPath and use addCurve method to create your view.
//1. Create this new Class
class ComplexView: UIView {
var path: UIBezierPath!
override init(frame: CGRect) {
super.init(frame: frame)
self.alpha = 0.3
complexShape()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
// Specify the fill color and apply it to the path.
UIColor.blue.setFill()
path.fill()
// Specify a border (stroke) color.
UIColor.magenta.setStroke()
path.stroke()
}
func complexShape() {
path = UIBezierPath()
path.move(to: CGPoint(x: 0.0, y: 0.0))
path.addCurve(to: CGPoint(x: 0, y: self.frame.size.height),
controlPoint1: CGPoint(x: 50.0, y: 25.0),
controlPoint2: CGPoint(x: 50.0, y: self.frame.size.height - 25.0))
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
self.backgroundColor = UIColor.orange
self.layer.mask = shapeLayer
}
}
In your ViewController call this View and add this view to your main view.
//2. In you Viewcontoller add ComplexView
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let width: CGFloat = 100.0
let height: CGFloat = 500.0
let complexView = ComplexView(frame: CGRect(x: 0,
y: self.view.frame.size.height/2 - height/2,
width: width,
height: height))
self.view.addSubview(complexView)
}
You need to play around addCurve method to get your desired shape.
Related
I would like to draw a triangle view and change the filled color programmatically.
Following is my code.
import UIKit
class ViewController: UIViewController {
let triangleView = TriangleView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
triangleView.frame = CGRect(x: 0,
y: 100,
width: 50,
height: 50)
self.view.addSubview(triangleView)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
triangleView.drawColor(color: .yellow)
}
}
class TriangleView: UIView {
let path = UIBezierPath()
override func draw(_ rect: CGRect) {
print("TriangleView draw")
path.move(to: CGPoint(x: 0, y: self.bounds.height / 2))
path.addLine(to: CGPoint(x: self.bounds.maxX, y: 0))
path.addLine(to: CGPoint(x: self.bounds.maxX, y: self.bounds.maxY))
path.addLine(to: CGPoint(x: 0, y: self.bounds.height / 2))
path.close()
self.drawColor(color: .green)
self.backgroundColor = .clear
}
func drawColor(color: UIColor) {
print("TriangleView drawColor")
color.setFill()
path.lineWidth = 0
path.fill()
path.stroke()
}
}
In this code, TriangleView draws a triangle filled with green color.
After that, ViewController changes filled color by yellow.
Following the result.
There are two problems.
Background color is black though expectation is clear.
Triangle color is not changed to yellow.
Could anyone give me advice ?
Use UIGraphicsGetCurrentContext
class TriangleView: UIView {
private var triangleColor: UIColor = .green {
didSet {
self.setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
print("TriangleView draw")
self.backgroundColor = .white // Set any background color
guard let context = UIGraphicsGetCurrentContext() else { return }
context.saveGState()
defer { context.restoreGState() }
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: self.bounds.height / 2))
path.addLine(to: CGPoint(x: self.bounds.maxX, y: 0))
path.addLine(to: CGPoint(x: self.bounds.maxX, y: self.bounds.maxY))
path.addLine(to: CGPoint(x: 0, y: self.bounds.height / 2))
path.close()
context.addPath(path.cgPath)
context.setFillColor(triangleColor.cgColor)
context.closePath()
context.closePath()
context.fillPath()
context.restoreGState()
}
func drawColor(color: UIColor) {
triangleColor = color
}
}
Or you can use CAShapeLayer
class TriangleView: UIView {
private let shapeLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
self.initialConfig()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initialConfig()
}
override func draw(_ rect: CGRect) {
print("TriangleView draw")
self.shapeLayer.frame = self.bounds
drawShape()
}
private func initialConfig() {
self.backgroundColor = .white
self.shapeLayer.fillColor = UIColor.green.cgColor
self.layer.addSublayer(shapeLayer)
}
private func drawShape() {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: self.bounds.height / 2))
path.addLine(to: CGPoint(x: self.bounds.maxX, y: 0))
path.addLine(to: CGPoint(x: self.bounds.maxX, y: self.bounds.maxY))
path.addLine(to: CGPoint(x: 0, y: self.bounds.height / 2))
path.close()
shapeLayer.path = path.cgPath
}
func drawColor(color: UIColor) {
self.shapeLayer.fillColor = color.cgColor
}
}
I have a UIView and I want to trim it with two circles, like I've drawn(sorry for the quality).
My code:
final class TrimmedView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
let size = CGSize(width: 70, height: 70)
let innerRadius: CGFloat = 366.53658283002471
let innerBottomRadius: CGFloat = 297.88543112651564
let path = UIBezierPath()
path.move(to: CGPoint(x: -innerRadius + (size.width / 2), y: innerRadius))
path.addArc(withCenter: CGPoint(x: size.width / 2, y: innerRadius), radius: innerRadius, startAngle: CGFloat.pi, endAngle: 0, clockwise: true)
path.move(to: CGPoint(x: -innerBottomRadius + (size.width / 2), y: innerBottomRadius))
path.addArc(withCenter: CGPoint(x: size.width / 2, y: innerBottomRadius), radius: innerBottomRadius, startAngle: 0, endAngle: CGFloat.pi, clockwise: true)
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.shadowPath = path.cgPath
layer.mask = shapeLayer
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
ViewController:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let view = UIView(frame: CGRect(origin: CGPoint(x: (self.view.bounds.width - 70) / 2, y: (self.view.bounds.height - 70) / 2), size: CGSize(width: 70, height: 70)))
view.backgroundColor = .red
self.view.addSubview(view)
let view1 = TrimmedView(frame: view.frame)
view1.backgroundColor = .yellow
self.view.addSubview(view1)
}
I got this result. It seems for me that top trimming works but the bottom doesn't and I don't know why. Any help would be appreciated. Thanks.
Here is a custom view that should give you what you want.
The UIBezierPath uses QuadCurves for the top "convex" arc and the bottom "concave" arc.
It is marked #IBDesignable so you can see it at design-time in IB / Storyboard. The "height" of the arc and the fill color are each set as #IBInspectable so you can adjust those values at design-time as well.
To use it in Storyboard:
Add a normal UIView
change the Class to BohdanShapeView
in the Attributes Inspector pane, set the Arc Offset and the Fill Color
set the background color as with a normal view (you'll probably use clear)
Result:
To use it via code:
let view1 = BohdanShapeView(frame: view.frame)
view1.fillColor = .systemTeal
view1.arcOffset = 10
self.view.addSubview(view1)
Here is the class:
#IBDesignable
class BohdanShapeView: UIView {
#IBInspectable var arcOffset: CGFloat = 0.0
#IBInspectable var fillColor: UIColor = UIColor.white
let shapeLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
// add the shape layer
layer.addSublayer(shapeLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
// fill color for the shape
shapeLayer.fillColor = self.fillColor.cgColor
let width = bounds.size.width
let height = bounds.size.height
let bezierPath = UIBezierPath()
// start at arcOffset below top-left
bezierPath.move(to: CGPoint(x: 0.0, y: 0.0 + arcOffset))
// add curve to arcOffset below top-right
bezierPath.addQuadCurve(to: CGPoint(x: width, y: 0.0 + arcOffset), controlPoint: CGPoint(x: width * 0.5, y: 0.0 - arcOffset))
// add line to bottom-right
bezierPath.addLine(to: CGPoint(x: width, y: height))
// add curve to bottom-left
bezierPath.addQuadCurve(to: CGPoint(x: 0.0, y: height), controlPoint: CGPoint(x: width * 0.5, y: height - arcOffset * 2.0))
// close the path
bezierPath.close()
shapeLayer.path = bezierPath.cgPath
}
}
I have this code to draw a rectangle which is rounded rect only on one side.
override func draw(_ rect: CGRect) {
// Drawing code
guard let context = UIGraphicsGetCurrentContext() else { return }
let lineWidth = CGFloat(4)
let pathRect = CGRect(x: 0, y: 0, width: rect.width, height: rect.height)
let path = UIBezierPath(roundedRect: pathRect.inset(by: UIEdgeInsets(top: lineWidth, left: lineWidth, bottom: lineWidth, right: 0)), byRoundingCorners: [.topLeft, .bottomLeft], cornerRadii: CGSize(width: 7, height: 7))
context.setFillColor(UIColor.black.cgColor)
path.fill()
context.setLineWidth(lineWidth)
}
I want to stroke it with red color on all but the right edge (no stroke on the right edge). How do I do it?
You’ll have to create your own path.
A couple of observations:
Don’t use the rect parameter. The rect is what is being asked to being drawn at this point in time, which may not be the entire view. Use bounds when figuring out what the overall path should be.
I might inset the path so that the stroke stays within the bounds of the view.
You can make this #IBDesignable if you want to also be able to see it rendered in IB.
You don’t really need UIGraphicsGetCurrentContext(). The UIKit methods fill(), stroke(), setFill(), and setStroke() methods automatically use the current context.
Thus:
#IBDesignable
class OpenRightView: UIView {
#IBInspectable var lineWidth: CGFloat = 4 { didSet { setNeedsDisplay() } }
#IBInspectable var radius: CGFloat = 7 { didSet { setNeedsDisplay() } }
#IBInspectable var fillColor: UIColor = .black { didSet { setNeedsDisplay() } }
#IBInspectable var strokeColor: UIColor = .red { didSet { setNeedsDisplay() } }
override func draw(_ rect: CGRect) {
let pathRect = bounds.inset(by: .init(top: lineWidth / 2, left: lineWidth / 2, bottom: lineWidth / 2, right: 0))
let path = UIBezierPath()
path.lineWidth = lineWidth
path.move(to: CGPoint(x: pathRect.maxX, y: pathRect.minY))
path.addLine(to: CGPoint(x: pathRect.minX + radius, y: pathRect.minY))
path.addQuadCurve(to: CGPoint(x: pathRect.minX, y: pathRect.minY + radius), controlPoint: pathRect.origin)
path.addLine(to: CGPoint(x: pathRect.minX, y: pathRect.maxY - radius))
path.addQuadCurve(to: CGPoint(x: pathRect.minX + radius, y: pathRect.maxY), controlPoint: CGPoint(x: pathRect.minX, y: pathRect.maxY))
path.addLine(to: CGPoint(x: pathRect.maxX, y: pathRect.maxY))
fillColor.setFill()
path.fill()
strokeColor.setStroke()
path.stroke()
}
}
That yields:
Theoretically, it might be more efficient to use CAShapeLayer and let Apple take care of the draw(_:) for us. E.g., they may have optimized the rendering to handle partial view updates, etc.
That might look like the following:
#IBDesignable
class OpenRightView: UIView {
#IBInspectable var lineWidth: CGFloat = 4 { didSet { updatePath() } }
#IBInspectable var radius: CGFloat = 7 { didSet { updatePath() } }
#IBInspectable var fillColor: UIColor = .black { didSet { shapeLayer.fillColor = fillColor.cgColor } }
#IBInspectable var strokeColor: UIColor = .red { didSet { shapeLayer.strokeColor = strokeColor.cgColor } }
lazy var shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = fillColor.cgColor
shapeLayer.strokeColor = strokeColor.cgColor
shapeLayer.lineWidth = lineWidth
return shapeLayer
}()
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
override func layoutSubviews() {
super.layoutSubviews()
updatePath()
}
}
private extension OpenRightView {
func configure() {
layer.addSublayer(shapeLayer)
}
func updatePath() {
let pathRect = bounds.inset(by: .init(top: lineWidth / 2, left: lineWidth / 2, bottom: lineWidth / 2, right: 0))
let path = UIBezierPath()
path.move(to: CGPoint(x: pathRect.maxX, y: pathRect.minY))
path.addLine(to: CGPoint(x: pathRect.minX + radius, y: pathRect.minY))
path.addQuadCurve(to: CGPoint(x: pathRect.minX, y: pathRect.minY + radius), controlPoint: pathRect.origin)
path.addLine(to: CGPoint(x: pathRect.minX, y: pathRect.maxY - radius))
path.addQuadCurve(to: CGPoint(x: pathRect.minX + radius, y: pathRect.maxY), controlPoint: CGPoint(x: pathRect.minX, y: pathRect.maxY))
path.addLine(to: CGPoint(x: pathRect.maxX, y: pathRect.maxY))
shapeLayer.path = path.cgPath
shapeLayer.lineWidth = lineWidth
}
}
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.
I am drawing a line between two points, but nothing is appearing on the view. I have search other s/o questions but can't seem to find a solution.
override func viewDidLoad() {
super.viewDidLoad()
self.drawLineFromPoint(point1: CGPoint(x: 10,y: 50), point2: CGPoint(x: 10,y: 80))
}
func drawLineFromPoint(point1:CGPoint, point2:CGPoint) {
let path = UIBezierPath()
path.move(to: point1)
path.addLine(to: point2)
let shapeLayer = CAShapeLayer()
shapeLayer.bounds = CGRect(x: 100, y: 100, width: 100, height: 100)
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = UIColor.green.cgColor
shapeLayer.lineWidth = 3
shapeLayer.fillColor = UIColor.clear.cgColor
self.view.layer.addSublayer(shapeLayer)
}
Try this code: tested in Swift 3.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let shapeLayer = ShapeView(frame: CGRect(x: 100, y: 200, width: 100, height: 100), shape: 0)
shapeLayer.backgroundColor = UIColor.clear
shapeLayer.layer.borderWidth = 5
shapeLayer.layer.borderColor = UIColor.red.cgColor
view.addSubview(shapeLayer)
}
Create a swift file and name it ShapeView.swift in UIView class and add the following code.
import UIKit
class ShapeView: UIView {
var currentShapeType: Int = 0
init(frame: CGRect, shape: Int) {
super.init(frame: frame)
self.currentShapeType = shape
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
let ctx = UIGraphicsGetCurrentContext()
ctx?.beginPath()
ctx?.move(to: CGPoint(x: 10.0, y: 50.0))
ctx?.addLine(to: CGPoint(x: 10.0, y: 80.0))
// ctx?.addLine(to: CGPoint(x: 100.0, y: 200.0))
ctx?.setLineWidth(3)
ctx?.closePath()
ctx?.strokePath()
}
}
Output:
Your problem seems to be like mine: you forgot to add these lines the end:
shapeLayer.stroke()
shapeLayer.fill()