Calling a Draw function in Swift - ios

I have the following code in my Draw2D class which is attached to the view in the viewcontroller.
In the viewcontroller I have a var Drawing = Draw2D(). I have a button hooked up which performs the function "Check" with self.Drawing.Check(). The problem is I can't get the line on the screen. The ellipse works fine and function Check performs well as I can see with println().
What is wrong?
import UIKit
class Draw2D: UIView {
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 4.0)
CGContextSetStrokeColorWithColor(context, UIColor.blueColor().CGColor)
let rectangle = CGRectMake(60,170,200,80)
CGContextAddEllipseInRect(context, rectangle)
CGContextStrokePath(context)
}
func Check() {
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 2.0)
CGContextMoveToPoint(context, CGFloat(100), CGFloat(300))
CGContextAddLineToPoint(context, CGFloat(100 + 200), CGFloat(300 + 100))
CGContextStrokePath(context)
}
}

Your call to Check() is outside of the regular rendering loop - I do not really know what UIGraphicsGetCurrentContext() actually returns in that case. I am guessing it will return nil:
The current graphics context is nil by default. Prior to calling its drawRect: method, view objects push a valid context onto the stack, making it current.
Anyway that would not change the fact that you cannot just call Check() and expect the line to be rendered. Assume for a second that the line was actually rendered: in the next rendering iteration you again only will do what is written inside drawRect and will therefore not display the line again.
What you have to do is create some kind of logic inside the drawRect to determine wether to call Check() or not.
A possible way to do this would be the following:
class Draw2D: UIView {
var callCheck:Bool? // changing it to Bool! or Bool will change the required check a few lines below
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 4.0)
CGContextSetStrokeColorWithColor(context, UIColor.blueColor().CGColor)
let rectangle = CGRectMake(60,170,200,80)
CGContextAddEllipseInRect(context, rectangle)
CGContextStrokePath(context)
if callCheck != nil && callCheck! {
Check()
}
}
func Check() {
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 2.0)
CGContextMoveToPoint(context, CGFloat(100), CGFloat(300))
CGContextAddLineToPoint(context, CGFloat(100 + 200), CGFloat(300 + 100))
CGContextStrokePath(context)
}
}
Alter your IBAction:
#IBAction func Button1(sender: AnyObject) {
self.Drawing.callCheck = true
self.Drawing.setNeedsDisplay()
}

Related

Calling next element in an Array

I'm trying to update the value in xColor, so it can grab a different color from colorPalette. As you can see, colorPalette[xColor].CGColor is doing the work to check what color to use. I'm having trouble trying to update xColor with the swipe gestures, so that it can grab a different element in the array. Any advice would be helpful!
Canvas.swift:
var xColor = 0
var colorPalette = [UIColor.blueColor(), UIColor.yellowColor(), UIColor.redColor(), UIColor.redColor()]
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
let color = colorPalette[xColor].CGColor
CGContextSetFillColorWithColor(context, color)
let rect = CGRect(x: x - r, y: y - r, width: 2 * r, height: 2 * r)
CGContextFillEllipseInRect(context, rect)
}
ViewController.swift:
func handleSwipes(sender:UISwipeGestureRecognizer) {
let cv = CanvasView()
if (sender.direction == .Left) {
cv.xColor--
print("Swipe left!")
}
if (sender.direction == .Right) {
cv.xColor++
print("Swipe right!")
}
print(cv.xColor)
}
There are two problems with your code. The first is this line:
let cv = CanvasView()
That creates a new CanvasView, but that new CanvasView is a different one from the old CanvasView that's already showing in the interface. Unfortunately, that old CanvasView is the one you need to talk to!
The second problem, once you're talking to the old CanvasView, is that you are failing to tell it that it needs redrawing. You need to send it the setNeedsDisplay message.

Overriding drawRect for drawing app

I am building a demo drawing application. I am using touchesMoved:withEvent to collect my points and adding them to a CGMutablePathRef. To stroke the path, I override DrawRect, add the path to the context and stroke the path:
override func drawRect(rect: CGRect) {
self.backgroundColor?.set()
UIRectFill(rect)
let context : CGContextRef = UIGraphicsGetCurrentContext()
for line in pathArray {
CGContextAddPath(context, line.structPath)
CGContextSetLineWidth(context, line.structLineWidth)
CGContextSetStrokeColorWithColor(context, line.structLineColor.CGColor)
CGContextSetAlpha(context, lineOpacity)
}
CGContextSetLineCap(context, kCGLineCapRound)
CGContextStrokePath(context)
self.empty = false
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first as UITouch! {
previousPoint = touch.previousLocationInView(self)
previousPreviousPoint = touch.previousLocationInView(self)
currentPoint = touch.locationInView(self)
}
self.touchesMoved(touches, withEvent: event)
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first as UITouch! {
previousPreviousPoint = previousPoint
previousPoint = touch.previousLocationInView(self)
currentPoint = touch.locationInView(self)
let mid1 : CGPoint = getMidPoint(previousPoint, p2: previousPreviousPoint)
let mid2 : CGPoint = getMidPoint(currentPoint, p2: previousPoint)
let subpath : CGMutablePathRef = CGPathCreateMutable()
CGPathMoveToPoint(subpath, nil, mid1.x, mid1.y)
CGPathAddQuadCurveToPoint(subpath, nil, previousPoint.x, previousPoint.y, mid2.x, mid2.y)
let bounds : CGRect = CGPathGetBoundingBox(subpath)
let drawBox : CGRect = CGRectInset(bounds, -2.0 * lineWidth, -2.0 * lineWidth)
let newLine = line(newPath: subpath)
pathArray.append(newLine)
self.setNeedsDisplayInRect(drawBox)
}
}
The above code works as expected, except I am seeing a unexpected result. The "draw box" which gets the bandbox and sets a CGRectInset changes the lineColor of other paths already drawn:
I understand (sort of) why this is happening, but cannot find a solution to this problem. Any suggestions would be most appreciated!
You want to stroke each path separately, like this:
override func drawRect(rect: CGRect) {
let context : CGContextRef = UIGraphicsGetCurrentContext()
// Set parameters in the context that are the same for all lines
CGContextSetLineCap(context, kCGLineCapRound)
CGContextSetAlpha(context, lineOpacity)
for line in pathArray {
// Set parameters in the context that are specific to this line
CGContextSetLineWidth(context, line.structLineWidth)
CGContextSetStrokeColorWithColor(context, line.structLineColor.CGColor)
// Add the line's path to the context's current path:
CGContextAddPath(context, line.structPath)
// And stroke it.
CGContextStrokePath(context)
// (This has the side effect of clearing the context's current path.)
}
}
The CGContext keeps track of a current path. Think of it as a CGMutablePath that you don't have direct access to. You can affect it by calling functions like CGContextAddPath or many others.
The CGContext's current path is not actually visible until you tell the context to draw it, by calling a function like CGContextStrokePath. At that time, the context uses the current path and the other values in the context (stroke color, alpha, line width, etc.) to figure out what to draw, and draws it.
Your code was adding each line's path into the current path, so you ended up with the current path containing several disconnected subpaths. Then you called CGContextStrokePath once at the end, and all of those subpaths were drawn using the values of the parameters that you most recently set. So you saw only the last line's width and color.

Set bool for UIButton Class in View controller (Swift)

I have a Custom Class for my UIButton. In this class I have the code below to draw the button. I would like the user to tap a button and toggle one part hidden/ not hidden. I don't know how to set the Bool value for this. Thanks
import UIKit
class CheckboxChecked: UIButton {
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
func drawTicked(#showTick: Bool) {
//// Rectangle Drawing
let rectanglePath = UIBezierPath(roundedRect: CGRectMake(3, 3, 34, 34), cornerRadius: 10)
UIColor.darkGrayColor().setStroke()
rectanglePath.lineWidth = 3
rectanglePath.stroke()
if (showTick) {
//// Bezier Drawing
var bezierPath = UIBezierPath()
bezierPath.moveToPoint(CGPointMake(9.5, 19.5))
bezierPath.addLineToPoint(CGPointMake(18.5, 26.5))
bezierPath.addLineToPoint(CGPointMake(30.5, 10.5))
bezierPath.lineCapStyle = kCGLineCapRound;
bezierPath.lineJoinStyle = kCGLineJoinRound;
UIColor.darkGrayColor().setStroke()
bezierPath.lineWidth = 3
bezierPath.stroke()
}
}
override func drawRect(rect: CGRect) {
// Drawing code
}
}
I would suggest that instead you make a new UIView with an internal UIButton component - as well as what ever other component you want to hide.
You can then add a click handler to your UIButton portion of the control which would then control the .visible state of the other component. In fact you could probably mock this up in a Playground.
And if you wanted to get really fancy you could look at #IBInspectible Check out this post on custom components: https://www.weheartswift.com/make-awesome-ui-components-ios-8-using-swift-xcode-6/
I managed to achieve this by using the code below.
I have a UIButton class with this:
override func drawRect(rect: CGRect) {
// Drawing code
//// Rectangle Drawing
let rectanglePath = UIBezierPath(roundedRect: CGRectMake(3, 3, 34, 34), cornerRadius: 10)
UIColor.darkGrayColor().setStroke()
rectanglePath.lineWidth = 3
rectanglePath.stroke()
if (darwCheck == true) {
//// Bezier Drawing
var bezierPath = UIBezierPath()
bezierPath.moveToPoint(CGPointMake(9.5, 19.5))
bezierPath.addLineToPoint(CGPointMake(18.5, 26.5))
bezierPath.addLineToPoint(CGPointMake(30.5, 10.5))
bezierPath.lineCapStyle = kCGLineCapRound;
bezierPath.lineJoinStyle = kCGLineJoinRound;
UIColor.darkGrayColor().setStroke()
bezierPath.lineWidth = 3
bezierPath.stroke()
}
}
And in the ViewController I had a button that changed a global Boolean variable and redrew the button.
drawCheck = true
checkboxButton.setNeedsDisplay()

Failing to stroke() the UIBezierPath

I wish to create a perfectly rounded rect (Circle) and paint it on the screen. I have tested my code in playground, and it successfully paints the UIBezierPath. It doesn't, however, successfully paint it in the iOS simulator. Here's the code I've been working on:
class Circles {
//Defining the rounded rect (Circle)
func roundRect(radius: CGFloat, angle: CGFloat) -> UIBezierPath {
//Creating the rounded the rectangle (Circle)
var roundedRect = UIBezierPath()
roundedRect.addArcWithCenter(CGPointZero, radius: radius,
startAngle: 0, endAngle: angle ,
clockwise: true)
return roundedRect
}
//Drawing the Bezier Path (Circle)
func drawRect(rect: UIBezierPath){
rect.moveToPoint(self.point)
UIColor.blackColor().setStroke()
rect.stroke()
}
//Giving the rounded rect (Circle) it's position
var point = CGPointMake(500, 500)
}
//Giving the rounded rect (Circle) it's size
var newRect = Circles().roundRect(200.0, angle: 7)
//Drawing the rounded rect (Circle)
Circles().drawRect(newRect)
I have seen some other posts with similar problems from a few years back, however they were in Objective-C, I tried translating but it was not of any use. I've also tried several other methods of painting the path on the screen but, again sadly, it was of no use. I tested it to make sure the functions are working with println statements, the issue is I don't know why the stroke is not activating. Thanks for reading, -Zach.
Here's the updated version using what Mike said:
class CircleView: UIView {
override func drawRect(rect: CGRect) {
// Creating the rectangle's size
var newRect = Circles().roundRect(200.0, angle: 7)
//Drawing the rectangle
Circles().drawRect(newRect)
}
//Holding all to do with the circle
class Circles {
//Defining the rounded rect (Circle)
func roundRect(radius: CGFloat, angle: CGFloat) -> UIBezierPath {
//Creating the rounded rect (Circle)
var roundedRect = UIBezierPath()
roundedRect.addArcWithCenter(CGPointZero, radius: radius,
startAngle: 0, endAngle: angle ,
clockwise: true)
return roundedRect
}
//Drawing the Bezier Path (Circle)
func drawRect(rect: UIBezierPath){
rect.moveToPoint(self.point)
UIColor.blackColor().setStroke()
UIColor.blackColor().setFill()
rect.stroke()
rect.fill()
}
//Giving the rounded rect (Circle) it's position
var point = CGPointMake(500, 500)
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//Generating the background
self.view.backgroundColor = UIColor(patternImage: UIImage(named: "normalpaper.jpg"))
let circleView = CircleView(frame: self.view.bounds)
self.view.addSubview(circleView)
}
As you mentioned in the comments, you're calling drawRect from a UIViewController's viewDidLoad function. You don't have a valid drawing context there, so that's not going to work.
The easiest way to make this work is to create a UIView subclass with its own drawRect function which calls Circle().drawRect(...) and add that as a subview of your UIViewController's view.
Here's an example of this:
Note: I've made the custom view transparent by setting circleView.opaque = false so that the background you mentioned in the comments shows through.
class CircleView: UIView {
override func drawRect(rect: CGRect) {
// I just copied this directly from the original question
var newRect = Circles().roundRect(200.0, angle: 7)
Circles().drawRect(newRect)
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Create a new CircleView that's the same size of this
// view controller's bounds and add it to the view hierarchy
let circleView = CircleView(frame: self.view.bounds)
circleView.opaque = false
self.view.addSubview(circleView)
}
}
Note: If you're going to be doing custom drawing, I highly recommend you read Drawing and Printing Guide for iOS. It'll teach you all the basics you need to make it work.

Allowing users to draw rect on a UIImage, with the intention of cropping the image

I'm sure this has been asked a number of times from various different perspectives, but I'm unable to find an answer on here as yet.
What I want to achieve
What I would like to do is to display a UIImage, and allow the user to draw a rectangle on the image, and eventually crop their selection.
Research so far
I've found previous questions here on SO that handle the cropping, however they often deal with static cropping areas that don't change, this does lead to the following constraints of such mechanism
The area of the image you're interested in may be positioned
anywhere, for example if you're trying to crop a road sign, it may
be centered on one image, but aligned left on another, therefore you
can't predict which area to crop.
The size and scale of the interested area may change, for example
one image may be a close up of that road sign so the cropping area
would be larger, but another image may have been taken from a
distance meaning the cropping area would be smaller.
With the combination of the above two variables, its almost impossible to accurately predict where the area of interest in the image would be, so I'm relying on the user to define this by being able to "draw" a box around the area we're interested in, in this case, a road sign.
This is all peachy on Android, since you can delegate all the hard work out with a nice intent, such as :
Intent intent = new Intent("com.android.camera.action.CROP");
However, I can't find an equivalent for iOS.
I've found this bit of code from this source :
- (UIImage *)imageByDrawingCircleOnImage:(UIImage *)image
{
// begin a graphics context of sufficient size
UIGraphicsBeginImageContext(image.size);
// draw original image into the context
[image drawAtPoint:CGPointZero];
// get the context for CoreGraphics
CGContextRef ctx = UIGraphicsGetCurrentContext();
// set stroking color and draw circle
[[UIColor redColor] setStroke];
// make circle rect 5 px from border
CGRect circleRect = CGRectMake(0, 0,
image.size.width,
image.size.height);
circleRect = CGRectInset(circleRect, 5, 5);
// draw circle
CGContextStrokeEllipseInRect(ctx, circleRect);
// make image out of bitmap context
UIImage *retImage = UIGraphicsGetImageFromCurrentImageContext();
// free the context
UIGraphicsEndImageContext();
return retImage;
}
Which I believe is a good starting point for cropping (it does crop circles however), it does rely on predefining the area you want to crop when calling CGRectMake.
This previous question also details how to do the actual cropping.
I'm assuming that to allow the user to draw the rect, I'd need to integrate with Gestures?
The Question :
How can I allow the user, to draw a rect over an image view, with the intent of cropping that area?
You could give BJImageCropper a try:
A simple UIView subclass that allows a user to crop an image. If you use it, I'd love to know! Twitter: #barrettjacobsen
This post is already 5 years old, but future references, this is how I managed to get it done. Following code is a combination of Rob's answer and some image cropping
Xcode 9 and Swift 4 are being used here
Add 2 ViewControllers
Add ImageView and 2 buttons for first view controller and another image view for last view controller
link all views to source file
View controller
import UIKit
extension UIView {
func snapshot(afterScreenUpdates: Bool = false) -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
drawHierarchy(in: bounds, afterScreenUpdates: afterScreenUpdates)
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
extension UIImage {
func crop( rect: CGRect) -> UIImage {
var rect = rect
rect.origin.x*=self.scale
rect.origin.y*=self.scale
rect.size.width*=self.scale
rect.size.height*=self.scale
let imageRef = self.cgImage!.cropping(to: rect)
let image = UIImage(cgImage: imageRef!, scale: self.scale, orientation: self.imageOrientation)
return image
}
}
class ViewController: UIViewController {
var rec: CGRect!
var cropImage: UIImage!
#IBOutlet weak var imageView: UIImageView!
private let shapeLayer: CAShapeLayer = {
let _shapeLayer = CAShapeLayer()
_shapeLayer.fillColor = UIColor.clear.cgColor
_shapeLayer.strokeColor = UIColor.green.cgColor
_shapeLayer.lineWidth = 2
return _shapeLayer
}()
private var startPoint: CGPoint!
override func viewDidLoad() {
super.viewDidLoad()
imageView.layer.addSublayer(shapeLayer)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
clear()
startPoint = touches.first?.location(in: imageView)
}
func clear() {
imageView.layer.sublayers = nil
imageView.image = UIImage(named: "aa")
imageView.layer.addSublayer(shapeLayer)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let startPoint = startPoint, let touch = touches.first else { return }
let point: CGPoint
if let predictedTouch = event?.predictedTouches(for: touch)?.last {
point = predictedTouch.location(in: imageView)
} else {
point = touch.location(in: imageView)
}
updatePath(from: startPoint, to: point)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let startPoint = startPoint, let touch = touches.first else { return }
let point = touch.location(in: imageView)
updatePath(from: startPoint, to: point)
imageView.image = imageView.snapshot(afterScreenUpdates: true)
shapeLayer.path = nil
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
shapeLayer.path = nil
}
private func updatePath(from startPoint: CGPoint, to point: CGPoint) {
let size = CGSize(width: point.x - startPoint.x, height: point.y - startPoint.y)
rec = CGRect(origin: startPoint, size: size)
shapeLayer.path = UIBezierPath(rect: rec).cgPath
}
#IBAction func btnTapped(_ sender: Any) {
clear()
}
#IBAction func btnCropTapped(_ sender: Any) {
let newRec = CGRect(origin: CGPoint(x: rec.origin.x, y: rec.origin.y), size: rec.size)
cropImage = imageView.image?.crop(rect: newRec)
print(rec)
print(newRec)
self.performSegue(withIdentifier: "toImage", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toImage" {
if let destination = segue.destination as? ImageViewController {
destination.croppedImage = cropImage
}
}
}
}

Resources