I want to transform my UIView's shape into this (the white UIView - look at the right edge) using Swift:
Any ideas how to do it? I guess I should use the transform functionality, but I don't know how exactly. Thanks in advance!
Update:
Here is the update, I misunderstood your question in my first answer
You need to subclass UIView and override the default draw method with your own behaviour. To Adjust the angle you can change the offsetFactor
class ShapeView : UIView {
public var bgc = UIColor.clear
override init(frame: CGRect) {
super.init(frame: frame)
//backup old background color
self.bgc = backgroundColor!
backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.bgc = backgroundColor!
backgroundColor = UIColor.clear
}
override func draw(_ rect: CGRect) {
let size = self.bounds.size
let offsetFactor = size.width * 0.98
//define the points
let tl = self.bounds.origin //top left
let tr = CGPoint(x:tl.x + size.width, y:tl.y) //top right
let br = CGPoint(x:tl.x + offsetFactor, y:tr.y + size.height) // bottom right
let bl = CGPoint(x:tl.x, y:tr.y + size.height) //bottom left
let path = UIBezierPath()
path.move(to: tl)
path.addLine(to: tr)
path.addLine(to: br)
path.addLine(to: bl)
path.close()
//set old background color
bgc.set()
path.fill()
}
}
Image
I hope I did understand your question right.
You need to subclass UIView and override the default draw method with your own behaviour. To Adjust the spire you can change the offsetFactor
This should do the job
class ShapeView : UIView {
public var bgc = UIColor.clear
override init(frame: CGRect) {
super.init(frame: frame)
//backup old background color
self.bgc = backgroundColor!
backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.bgc = backgroundColor!
backgroundColor = UIColor.clear
}
override func draw(_ rect: CGRect) {
let size = self.bounds.size
let offsetFactor = size.height * 0.7
//define the points
let tl = self.bounds.origin //top left
let tr = CGPoint(x:tl.x + size.width, y:tl.y) //top right
let br = CGPoint(x:tr.x, y:tr.y + offsetFactor) // bottom right
let bm = CGPoint(x:size.width/2, y:size.height) ////bottom middle
let bl = CGPoint(x:tl.x, y:offsetFactor) //bottom left
let path = UIBezierPath()
path.move(to: tl)
path.addLine(to: tr)
path.addLine(to: br)
path.addLine(to: bm)
path.addLine(to: bl)
path.close()
//set old background color
bgc.set()
path.fill()
}
}
Storyboard and Simulator
Dudes, I found an easier solution that works perfectly as an extension to UIView:
extension UIView {
func makeItEdgy() {
let bounds = self.bounds
// Create path to mask the view
let path = CGMutablePath()
path.move(to: bounds.origin)
path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.minY))
path.addLine(to: CGPoint(x: bounds.maxX - (bounds.height / 5), y: bounds.maxY))
path.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
path.closeSubpath()
let maskLayer = (self.layer.mask as? CAShapeLayer) ?? CAShapeLayer()
maskLayer.path = path
self.layer.mask = maskLayer
}
}
Related
I want to set image inside this path (i.e) I want imageview instead of red colour
enter image description here
Here is my code
import UIKit
#IBDesignable
class diagonalviewprofile: UIView {
#IBInspectable var color : UIColor? = UIColor.red {
didSet {
// self.layer.backgroundColor = self.color?.cgColor
}
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
backgroundColor = UIColor.clear
}
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
//get the size of the view
let size = self.bounds.size
//get 4 points for the shape layer
let p1 = self.bounds.origin
let p2 = CGPoint(x: p1.x + size.width, y: p1.y)
let p3 = CGPoint(x: p2.x, y: size.height - 150)
let p4 = CGPoint(x: p1.x, y: size.height - 30)
//create the path
let path = UIBezierPath()
path.move(to: p1)
path.addLine(to: p2)
path.addLine(to: p3)
path.addLine(to: p4)
path.close()
(color ?? UIColor.red).set()
path.fill()
}
}
I want to set image inside this path (i.e) I want imageview instead of red colour
You can set image like:
// get an image of the graphics context
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
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 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.
I tried with many ways to draw buttons as shown in image, But I'm able to draw only hexagonal . Am not able to draw as show in image . Please help me how to draw as shown in image. Thank you
You can do this with UIBezierPath and CAShapeLayer
Create a UIButton subclass and override layoutSubviews(), which is where you'll update your path, connecting the points with lines.
Here is a starting point for you:
class AngledButton: UIButton {
var bPath = UIBezierPath()
var theShapeLayer = CAShapeLayer()
var fillColor: UIColor = UIColor.white
var borderColor: UIColor = UIColor.gray
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
layer.addSublayer(theShapeLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
let p1 = CGPoint.zero
let p2 = CGPoint(x: bounds.width - bounds.height * 0.3, y: 0.0)
let p3 = CGPoint(x: bounds.width, y: bounds.height * 0.5)
let p4 = CGPoint(x: bounds.width - bounds.height * 0.3, y: bounds.height)
let p5 = CGPoint(x: 0.0, y: bounds.height)
bPath.move(to: p1)
bPath.addLine(to: p2)
bPath.addLine(to: p3)
bPath.addLine(to: p4)
bPath.addLine(to: p5)
bPath.close()
theShapeLayer.path = bPath.cgPath
theShapeLayer.fillColor = self.fillColor.cgColor
theShapeLayer.strokeColor = self.borderColor.cgColor
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// only allow tap *within* the bezier path
if bPath.contains(point) {
return self
}
return nil
}
}
By also overriding hitTest() the button will only be tapped if the tap is within the bezier path area. You can then overlap your buttons, and a tap at the upper-right corner will "pass through" to the button underneath.
Result:
Notes:
You can make this #IBDesignable to see the layout in Interface Builder
You'll need to play with the Title Insets to get the title labels to account for the angled edge
This is just a starting point, but should get you on your way
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.