How to draw a line between two views in Swift 3? - swift

I need help with drawing a simple line between two Outline Views in Swift 3 (Xcode 8).
My situation:
Main ViewController
|--- Main View
|--- Outline View
|--- Outline View
So I Need help to get the coordinates of both Outline Views and draw a line with them (the line itself is not that difficult, more to get the coordinates). The goal is to draw a line (programmatically) that connects both Outline Views (f.ex. from one edge to the other, or from the top, ...).
I already tried following:
class Line: NSView{
var origin = CGPoint()
var destination = CGPoint()
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
}
init(fromPoint: CGPoint, toPoint: CGPoint){
self.origin = fromPoint
self.destination = toPoint
super.init(frame: CGRect(origin: fromPoint, size: CGSize(width: destination.x - origin.x, height: destination.y - origin.y)))
}
override func draw(_ dirtyRect: NSRect){
let myPath = NSBezierPath()
myPath.move(to: CGPoint(x: origin.x, y: origin.y))
myPath.line(to: CGPoint(x: destination.x - origin.x, y: destination.y - origin.y))
myPath.stroke()
}
}
class ViewController: NSViewController{
override func viewDidLoad(){
super.viewDidLoad()
let line = Line(fromPoint: self.view.convert(CGPoint.zero, to: self.view.viewWithTag(1)), toPoint: self.view.convert(CGPoint.zero, to: self.view.viewWithTag(2)))
view.addSubview(line)
}
}
But that didn't do anything.
I would appreciate your help!
Thank you

I now solved my problem (more or less) as following:
class Line: NSView{
var fromPoint = CGPoint()
var toPoint = CGPoint()
func setPoints(fromPoint: CGPoint, toPoint: CGPoint){
self.fromPoint = fromPoint
self.toPoint = toPoint
}
override func draw(_ dirtyRect: NSRect) {
let path = NSBezierPath()
NSColor.green.setFill()
path.move(to: fromPoint)
path.line(to: toPoint)
path.stroke()
}
}
class ViewController: NSViewController{
override function viewDidLoad(){
super.viewDidLoad()
let subview3 = Line(frame: self.view.bounds)
subview3.setPoints(fromPoint: subview1.convert(CGPoint(x: subview1.bounds.maxX, y: subview1.bounds.maxY), to: self.view), toPoint: subview2.convert(CGPoint(x: subview2.bounds.minX, y: subview2.bounds.minY), to: self.view))
self.view.addSubview(subview3)
}
}
I need to know how to do this on runtime. Do I always have to create a new view in order to draw a path?
A full example:
//
// ViewController.swift
// DrawConnectViews
//
// Created by T M on 17.06.17.
// Copyright © 2017 TM. All rights reserved.
//
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let subview1 = CustomViewWithColor(frame: NSRect(origin: CGPoint(x: 10.0, y: 10.0), size: CGSize(width: 200.0, height: 200.0)))
let subview2 = CustomViewWithColor(frame: NSRect(origin: CGPoint(x: 360.0, y: 360.0), size: CGSize(width: 200.0, height: 200.0)))
// create a subview programatically:
let subview3 = Line(frame: self.view.bounds)
subview3.setPoints(fromPoint: subview1.convert(CGPoint(x: subview1.bounds.maxX, y: subview1.bounds.maxY), to: self.view), toPoint: subview2.convert(CGPoint(x: subview2.bounds.minX, y: subview2.bounds.minY), to: self.view))
self.view.addSubview(subview3)
subview1.setColor(color: NSColor.red)
subview2.setColor(color: NSColor.blue)
self.view.addSubview(subview1)
self.view.addSubview(subview2)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
class CustomViewWithColor: NSView{
var color = NSColor()
func setColor(color: NSColor){
self.color = color
}
override func draw(_ dirtyRect: NSRect) {
let path = NSBezierPath(rect: self.bounds)
self.color.setFill()
path.fill()
}
}
class Line: NSView{
var fromPoint = CGPoint()
var toPoint = CGPoint()
func setPoints(fromPoint: CGPoint, toPoint: CGPoint){
self.fromPoint = fromPoint
self.toPoint = toPoint
}
override func draw(_ dirtyRect: NSRect) {
let path = NSBezierPath()
NSColor.green.setFill()
path.move(to: fromPoint)
path.line(to: toPoint)
path.stroke()
}
}
That produces following:
Output of program

A lot of people will think that this is overkill, but what I do is add a subview whose background is the line color, whose height is constrained to the desired line thickness, and whose leading and trailing edges are constrained to the superview's leading and trailing edges. This ensures that the border will always adjust with the superview's size, which is why adding a border as a layer or customizing the view's draw(in:) to draw the border as a path don't work as well.

Related

Drag a CGRect using UIPanGestureRecognizer

I am subclassing UIView, and am trying to "drag" a CGRect back and forth across the screen. Basically I want to move(redraw) the rectangle every time I drag my finger. So far, I have this code:
var rectangle: CGRect {
get {
return CGRect(x: 200,
y: 200,
width: frame.width / 6,
height: 15)
}
set {}
}
override func draw(_ rect: CGRect) {
let gesture = UIPanGestureRecognizer(target: self, action: #selector(dragRectangle(recognizer:)))
addGestureRecognizer(gesture)
drawRectangle(rect)
}
func drawRectangle(_ rect: CGRect) {
let path = UIBezierPath(rect: rectangle)
UIColor.black.set()
path.fill()
}
#objc func dragRectangle(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self)
rectangle = CGRect(x: rectangle.midX + translation.x, y: rectangle.midY + translation.y, width: rectangle.width, height: rectangle.height)
setNeedsDisplay()
recognizer.setTranslation(CGPoint.zero, in: self)
}
This is my first time using UIPanGestureRecognizer, so I'm not sure of all the details that go into this. I have set breakpoints in drawRectangle and confirmed that this is being called. However, the rectangle on the screen does not move at all, no matter how many times I try to drag it. What's wrong?
This is how you can do it easily. just copy paste and run this.
//
// RootController.swift
// SampleApp
//
// Created by Chanaka Caldera on 24/6/19.
// Copyright © 2019 homeapps. All rights reserved.
//
import UIKit
class RootController: UIViewController {
private var draggableView: UIView!
private var pangesture: UIPanGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
setdragview()
}
}
extension RootController {
fileprivate func setdragview() {
draggableView = UIView()
draggableView.translatesAutoresizingMaskIntoConstraints = false
draggableView.backgroundColor = .lightGray
view.addSubview(draggableView)
let draggableviewConstraints = [draggableView.leftAnchor.constraint(equalTo: view.leftAnchor,constant: 10),
draggableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 64),
draggableView.widthAnchor.constraint(equalToConstant: 100),
draggableView.heightAnchor.constraint(equalToConstant: 40)]
NSLayoutConstraint.activate(draggableviewConstraints)
pangesture = UIPanGestureRecognizer()
draggableView.isUserInteractionEnabled = true
draggableView.addGestureRecognizer(pangesture)
pangesture.addTarget(self, action: #selector(draggableFunction(_:)))
}
}
extension RootController {
#objc fileprivate func draggableFunction(_ sender: UIPanGestureRecognizer) {
view.bringSubviewToFront(draggableView)
let translation = sender.translation(in: self.view)
draggableView.center = CGPoint(x: draggableView.center.x + translation.x, y: draggableView.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: self.view)
print("drag works : \(translation.x)")
}
}
here is the demo,
Hope this will help. cheers !
Try like this (check comments through code):
#IBDesignable
class Rectangle: UIView {
#IBInspectable var color: UIColor = .clear {
didSet { backgroundColor = color }
}
// draw your view using the background color
override func draw(_ rect: CGRect) {
backgroundColor?.set()
UIBezierPath(rect: rect).fill()
}
// add the gesture recognizer to your view
override func didMoveToSuperview() {
addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(pan)))
}
// your gesture selector
#objc func pan(_ gesture: UIPanGestureRecognizer) {
// update your view frame origin
frame.origin += gesture.translation(in: self)
// reset the gesture translation
gesture.setTranslation(.zero, in: self)
}
}
extension CGPoint {
static func +=(lhs: inout CGPoint, rhs: CGPoint) {
lhs.x += rhs.x
lhs.y += rhs.y
}
}
To draw rectangles on your view when panning you can do as follow:
import UIKit
class ViewController: UIViewController {
var rectangles: [Rectangle] = []
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(pan)))
}
#objc func pan(_ gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .began:
let rectangle = Rectangle(frame: .init(origin: gesture.location(in: view), size: .init(width: 0, height: 0)))
rectangle.fillColor = .red
rectangle.strokeColor = .white
rectangle.lineWidth = 3
view.addSubview(rectangle)
rectangles.append(rectangle)
case .changed:
let distance = gesture.translation(in: view)
let index = rectangles.index(before: rectangles.endIndex)
let frame = rectangles[index].frame
rectangles[index].frame = .init(origin: frame.origin, size: .init(width: frame.width + distance.x, height: frame.height + distance.y))
rectangles[index].setNeedsDisplay()
gesture.setTranslation(.zero, in: view)
case .ended:
break
default:
break
}
}
}
Sample Project
I figured out most of the problem as to why the rectangle wasn't moving. It turns out that I misunderstood how variable getters and setters work in Swift. Nevertheless, I changed this code:
var rectangle: CGRect {
get {
return CGRect(x: 200,
y: 200,
width: frame.width / 6,
height: 15)
}
set {}
}
to be a lazy variable:
lazy var rectangle: CGRect = {
return CGRect(x: 200,
y: 200,
width: self.frame.width / 6,
height: 15)
}()
The reason I needed get and set in the first place was because I use frame in my variable calculations, and that was not available until the view itself was fully instantiated. I also tweaked this code a bit:
rectangle = CGRect(x: rectangle.midX + translation.x, y: rectangle.midY + translation.y, width: rectangle.width, height: rectangle.height)
and used minX instead of midX:
rectangle = CGRect(x: rectangle.minX + translation.x, y: rectangle.minY + translation.y, width: rectangle.width, height: rectangle.height)
This is because CGRect is initialized with the x and y parameters being the minX and minY.
This is good progress(at least the rectangle moves). However, I am not able to figure out why the rectangle only switches places after I have released my mouse, resulting in choppy movement.

Why are these CAShapeLayers not going where expected?

I'm working on a custom loading indicator and am having a lot of issues with CAShapeLayers.
The loading indicator will be contained within a custom UIView so that any viewController can use it.
First issue:
The frame of the subview is not matching the bounds.
When using this code to display a circle in each corner of the frame the circles are placed in a square shape but it is no where near the view.
import UIKit
View Controller:
class MergingCicles: UIViewController, HolderViewDelegate {
func animateLabel() {
}
var holderView = HolderView(frame: CGRect.zero)
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
addHolderView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func addHolderView() {
let boxSize: CGFloat = 100.0
holderView.frame = CGRect(x: view.bounds.width / 2 - boxSize / 2,
y: view.bounds.height / 2 - boxSize / 2,
width: boxSize,
height: boxSize)
holderView.parentFrame = view.frame
holderView.delegate = self
holderView.center = self.view.center
view.addSubview(holderView)
holderView.addCircleLayer()
}
}
Subview:
Import UIKit
protocol HolderViewDelegate:class {
func animateLabel()
}
class HolderView: UIView {
let initalLayer = InitialLayer()
let triangleLayer = TriangleLayer()
let redRectangleLayer = RectangleLayer()
let blueRectangleLayer = RectangleLayer()
let arcLayer = ArcLayer()
var parentFrame :CGRect = CGRect.zero
weak var delegate:HolderViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.clear
}
required init(coder: NSCoder) {
super.init(coder: coder)!
}
func addCircleLayer() {
var circleLocations = [CGPoint]()
let offset = CircleLayer().maxSize / 2
circleLocations.append(CGPoint(x: self.frame.maxX - offset, y: self.frame.maxY - offset))
circleLocations.append(CGPoint(x: self.frame.minX + offset, y: self.frame.minY + offset))
circleLocations.append(CGPoint(x: self.frame.maxX - offset, y: self.frame.minY + offset))
circleLocations.append(CGPoint(x: self.frame.minX + offset, y: self.frame.maxY - offset))
circleLocations.append(layer.anchorPoint)
for point in circleLocations {
let circle = CircleLayer()
circle.updateLocation(Size: .medium, center: point)
self.layer.addSublayer(circle)
}
self.backgroundColor = .blue
// layer.anchorPoint = CGPoint(x: (self.bounds.maxX + self.bounds.maxX)/2, y: (self.bounds.maxY + self.bounds.minY)/2)
let rotationAnimation: CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.toValue = CGFloat(Double.pi * 2)
rotationAnimation.duration = 0.45
rotationAnimation.isCumulative = true
//rotationAnimation.repeatCount = 1000
//rotationAnimation.isRemovedOnCompletion = true
// layer.add(rotationAnimation, forKey: nil)
}
}
Circle Layer:
import Foundation
import UIKit
enum ShapeSize {
case medium
case small
case large
}
class CircleLayer: CAShapeLayer {
let animationDuration: CFTimeInterval = 0.3
let maxSize = CGFloat(50)
override init() {
super.init()
fillColor = UIColor.red.cgColor
}
func updateLocation(Size: ShapeSize, center: CGPoint){
switch Size {
case .medium:
path = UIBezierPath(ovalIn: CGRect(x: center.x, y: center.y, width: maxSize/3, height: maxSize/3)).cgPath
case .small:
path = UIBezierPath(ovalIn: CGRect(x: center.x, y: center.y, width: (2*maxSize)/3, height: (2*maxSize)/3)).cgPath
case .large:
path = UIBezierPath(ovalIn: CGRect(x: center.x, y: center.y, width: maxSize, height: maxSize)).cgPath
}
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Result:
This really shows that the frame is no where near the uiView.
If I change addCircleLayer to use bounds instead I get something much closer:
But still the circles are not in the corners (except the bottom right one, that one is correct). It appears there is some extra space on the left and top of the view that is not captured using self.bounds.
The ultimate goal is to also rotate the circles 360 degrees around the center but as shown by the circle in the upper left corner the layer anchor is not in the center of the view, I changed the anchor to be the center of the circles but then nothing appeared on screen at all.
You're saying things like
circleLocations.append(CGPoint(x: self.frame.maxX - offset, y: self.frame.maxY - offset))
But self.frame is where the view is located in its own superview. Thus, the shape layer ends up offset from the view by as much as the view is offset from its own superview. Wherever you say frame here, you mean bounds.
I found the problem was then when drawing the circles I was using UIBezierPath(ovalIn:CGRect, width: CGFloat, height: CGFloat which was using the x value for the left side of the circle. When I changed to UIBezierPath(arcCenter: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool) the point was used for the center of the circle and made it all fit where expected when using self.bounds to calculate the points.
After that I no longer had to change the anchor point as it was in the correct location by default.
I didn't figure out why the frame is in a completely different spot but it is no longer impacting the project.

Draw Circle where user clicks with UIBezierPath

I am new to swift. I want to draw Circle on those pixels where user clicks.
Here is my code it is drawing circle but not at where I clicks....
I want to draw circle where user clicks....Like it is getting the coordinates where user clicks and printing them to console but I want to update the x and y arguments in ovalsandcircles() function.
Thanks in advance.
`import UIKit
class DemoView: UIView {
var startX :CGFloat = 0.0
var startY :CGFloat = 0.0
var path: UIBezierPath!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.darkGray
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
// Specify the fill color and apply it to the path.
ovalsAndCircles()
UIColor.orange.setFill()
path.fill()
Specify a border (stroke) color.
UIColor.purple.setStroke()
path.stroke()
}
func ovalsAndCircles () {
self.path = UIBezierPath(ovalIn: CGRect(x: startX,
y: startY,
width: 200,
height: 200))
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let point = touches.first!.location(in: self)
startX = point.x
startY = point.y
print(startY)
print(startX)
}
}
`
(1) Add a UIView control through Interface Builder.
(2) Set the class name of that UIView control to DemoView.
(3) Create a subclass of UIView as DemoView as follows.
import UIKit
class DemoView: UIView {
let fillColor = UIColor.green
let strokeColor = UIColor.black
let radius: CGFloat = 100.0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = event?.allTouches?.first
if let touchPoint = touch?.location(in: self) {
drawCircle(point: touchPoint)
}
}
func drawCircle(point: CGPoint) {
if let subLayers = self.layer.sublayers {
for subLayer in subLayers {
subLayer.removeFromSuperlayer()
}
}
let circlePath = UIBezierPath(arcCenter: point, radius: radius, startAngle: CGFloat(0), endAngle: CGFloat(Double.pi * 2.0), clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
shapeLayer.fillColor = fillColor.cgColor
shapeLayer.strokeColor = strokeColor.cgColor
self.layer.addSublayer(shapeLayer)
}
}
First look at the Coordinate System in iOS: https://developer.apple.com/library/archive/documentation/General/Conceptual/Devpedia-CocoaApp/CoordinateSystem.html
The CGRect has a origin property of type CGPoint. Maybe it helps to set the origin of your rect in your draw function:
let rect: CGRect = ....
rect.origin = CGPoint(x:0.5, y:0.5) //Set the origin to the middle of the rect
You call the touchesBegan Method to set the points where it should be drawn. But when the user moves over the screen it wont be recognized. Use touchesEnded Method instead

Drawing an NSBezierPath Shape with NSView Subclass

I'm trying to draw a shape that is created with NSBezierPath on an NSView canvas. I've created a subclass of NSView.
// NSView //
import Cocoa
class DisplayView: NSView {
var path: NSBezierPath
var fillColor: NSColor
var strokeColor: NSColor
var weight: CGFloat
init(frame: CGRect, path: NSBezierPath, fillColor: NSColor, strokeColor: NSColor, weight: CGFloat){
self.path = path
self.fillColor = fillColor
self.strokeColor = strokeColor
self.weight = weight
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ dirtyRect: NSRect) {
path.lineWidth = weight
fillColor.set()
path.fill()
strokeColor.set()
path.stroke()
}
}
// NSViewController //
import Cocoa
class ViewController: NSViewController {
// MARK: - IBOutlet
#IBOutlet weak var canvasView: NSView!
override func viewDidLoad() {
super.viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
let path = Shapes.circle(maxSize: 100) // a path from a separate class
let rect = CGRect(x: 20, y: 20, width: 200, height: 200)
let pathView = DisplayView(frame: rect, path: path, fillColor: NSColor.green, strokeColor: NSColor.white, weight: 6.0)
canvasView.addSubview(pathView)
}
}
And I get the following result. How come the edges are broken by half the line weight on two sides? The path object is only a size of 100 pts x 100 pts. Thanks.
UPDATE
The following is the code for making a path object.
class Shapes {
static func circle(maxSize: CGFloat) -> NSBezierPath {
let oval = NSBezierPath.init(ovalIn: CGRect(x: 0.0 * maxSize, y: 0.0 * maxSize, width: 1.0 * maxSize, height: 1.0 * maxSize))
oval.close()
return oval
}
}
You have created your bezier path with an origin of 0,0. As a result, the thicker border gets clipped by the view it is rendered in (your DisplayView class).
You need to create your path so the origin is (weight / 2 + 1) instead of 0.
Or you can apply a translate transform to the graphics context and shift the origin by (weight / 2 + 1.
override func draw(_ dirtyRect: NSRect) {
let ctx = NSGraphicsContext.current!.cgContext
ctx.saveGState()
let offset = weight / 2 + 1
ctx.translateBy(x: offset, y: offset)
path.lineWidth = weight
fillColor.set()
path.fill()
strokeColor.set()
path.stroke()
ctx.restoreGState()
}

Swift 3: Drawing a rectangle

I'm 3 days new to swift, and I'm trying to figure out how to draw a rectangle. I'm too new to the language to know the classes to extend and the methods to override, and I've looked around for sample code, but nothing seems to work (which I'm attributing to my use of swift 3).
What I'm trying now is:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let k = Draw(frame: CGRect(
origin: CGPoint(x: 50, y: 50),
size: CGSize(width: 100, height: 100)))
k.draw(CGRect(
origin: CGPoint(x: 50, y: 50),
size: CGSize(width: 100, height: 100)));
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
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
var color:UIColor = UIColor.yellow()
var drect = CGRect(x: (w * 0.25),y: (h * 0.25),width: (w * 0.5),height: (h * 0.5))
var bpath:UIBezierPath = UIBezierPath(rect: drect)
color.set()
bpath.stroke()
print("it ran")
NSLog("drawRect has updated the view")
}
}
And that's not doing anything. Help.
In order to see the view, you need to create one and give it frame so that it knows how big to make it.
If you put your code in a Playground, and then add this line:
let d = Draw(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
You'll be able to click on the Quick View on the right, and then you'll see the view.
You can also add the view as a subview of view in your ViewController and then you'll see it on the iPhone:
override func viewDidLoad() {
super.viewDidLoad()
let k = Draw(frame: CGRect(
origin: CGPoint(x: 50, y: 50),
size: CGSize(width: 100, height: 100)))
// Add the view to the view hierarchy so that it shows up on screen
self.view.addSubview(k)
}
Note that you never call draw(_:) directly. It is called for you by Cocoa Touch to display the view.
Create a class, I put it in a separate Swift 3 file.
//
// Plot_Demo.swift
//
// Storyboard is not good in creating self adapting UI
// Plot_Demo creates the drawing programatically.
import Foundation
import UIKit
public class Plot_Demo: UIView
{
override init(frame: CGRect) {
super.init(frame: frame)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func draw(_ frame: CGRect) {
let h = frame.height
let w = frame.width
let color:UIColor = UIColor.yellow
let drect = CGRect(x: (w * 0.25), y: (h * 0.25), width: (w * 0.5), height: (h * 0.5))
let bpath:UIBezierPath = UIBezierPath(rect: drect)
color.set()
bpath.stroke()
print("it ran")
NSLog("drawRect has updated the view")
}
}
Example of use in an UIViewController object:
override func viewDidLoad() {
super.viewDidLoad()
// Instantiate a new Plot_Demo object (inherits and has all properties of UIView)
let k = Plot_Demo(frame: CGRect(x: 75, y: 75, width: 150, height: 150))
// Put the rectangle in the canvas in this new object
k.draw(CGRect(x: 50, y: 50, width: 100, height: 100))
// view: UIView was created earlier using StoryBoard
// Display the contents (our rectangle) by attaching it
self.view.addSubview(k)
}
Run in an iPhone simulator and on an iPhone:
Used XCode Version 8.0 (8A218a), Swift 3, target iOS 10.0
This is another way of drawing Rectangle,
Step 1: Get rectangle path for given points
(Note: arrPathPoints must be 4 in count to draw rectangle),
func getPathPayer(arrPathPoints:[CGPoint]) throws -> CAShapeLayer {
enum PathError : Error{
case moreThan2PointsNeeded
}
guard arrPathPoints.count > 2 else {
throw PathError.moreThan2PointsNeeded
}
let lineColor = UIColor.blue
let lineWidth: CGFloat = 2
let path = UIBezierPath()
let pathLayer = CAShapeLayer()
for (index,pathPoint) in arrPathPoints.enumerated() {
switch index {
//First point
case 0:
path.move(to: pathPoint)
//Last point
case arrPathPoints.count - 1:
path.addLine(to: pathPoint)
path.close()
//Middle Points
default:
path.addLine(to: pathPoint)
}
}
pathLayer.path = path.cgPath
pathLayer.strokeColor = lineColor.cgColor
pathLayer.lineWidth = lineWidth
pathLayer.fillColor = UIColor.clear.cgColor
return pathLayer
}
Step 2: Usage, Call method like this,
override func viewDidLoad() {
super.viewDidLoad()
do {
let rectangleLayer = try getPathPayer(arrPathPoints: [
CGPoint(x: 110, y: 110), //Top-Left
CGPoint(x: 130, y: 110), //Top-Right
CGPoint(x: 130, y: 130), //Bottom-Right
CGPoint(x: 110, y: 130)]) //Bottom-Left
view.layer.addSublayer(rectangleLayer)
} catch {
debugPrint(error)
}
}
My version of how to draw a rectangle using Swift 5.
First create a class to do the drawing. It uses CoreGraphics to do the drawing, rather than UIKit.
import UIKit
class DrawRectangle: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else {
print("could not get graphics context")
return
}
context.setStrokeColor(UIColor.yellow.cgColor)
context.setLineWidth(2)
context.stroke(rect.insetBy(dx: 10, dy: 10))
}
}
Then put this in your ViewController's viewDidLoad()
let myView = DrawRectangle(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
self.view.addSubview(myView)