Create an image in code and display it into UIImageView - ios

I am trying to create an image. By checking the size, I can see that image has been created. But I am unable to assign that image as image property of UIImageView. Please help.
let imageRenderer=UIGraphicsImageRenderer(size: self.view.bounds.size)
book = imageRenderer.image { c in
let ctx=c.cgContext
ctx.setStrokeColor(UIColor.black.cgColor)
ctx.setLineWidth(5)
let x=self.view.bounds.maxX
let y=self.view.bounds.maxY
ctx.move(to: CGPoint(x:x/16,y:x/8))
ctx.addLine(to: CGPoint(x: x/2, y: x/4))
ctx.addLine(to: CGPoint(x: x-x/16, y: x/8))
ctx.addLine(to:CGPoint(x: x-x/16, y: y-x/8))
ctx.addLine(to: CGPoint(x: x/2, y: y-x/16))
ctx.addLine(to: CGPoint(x: x/16, y: y-x/8))
ctx.closePath()
}
self.image.image = book!

You just add lines but not paint color for them.
You can pain color for them by calling
ctx.strokePath()
right after (inside the closure)
ctx.closePath()
More information you can read here
https://developer.apple.com/documentation/coregraphics/cgcontext/1454490-strokepath

Related

BezierPath not showing on button tap - CGContextSetStrokeColorWithColor: invalid context 0x0

How to draw a BezierPath on a button tap?
I'm practicing with BezierPaths. What I'm trying to do is draw a BezierPaths shape when a button is tapped but I'm not seeing anything on the screen when I tap the button. I'm not getting any errors just the output in the debug area shown below.
What am I missing?
Here is my code:
#IBAction func drawIt(_ sender: Any) {
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 46.5, y: 50.14))
bezierPath.addCurve(to: CGPoint(x: 77.5, y: 50.5), controlPoint1: CGPoint(x: 46.5, y: 50.14), controlPoint2: CGPoint(x: 64.74, y: 34.09))
bezierPath.addCurve(to: CGPoint(x: 94.5, y: 96.5), controlPoint1: CGPoint(x: 84.5, y: 59.5), controlPoint2: CGPoint(x: 88.29, y: 74.25))
UIColor.black.setStroke()
bezierPath.lineWidth = 1
bezierPath.stroke()
}
Debug Area:
CGContextSetStrokeColorWithColor: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
Everything was okay until you said:
bezierPath.stroke()
Up until then, you were just making a path, which is a neutral set of instructions. But when you said stroke(), you were saying: "Okay, now draw it, baby!" To which the runtime is now replying: "Uh, where? You can only draw in some kind of drawing (graphics) context, such as a UIView's graphics context, a CALayer's graphics context, or a UIImage context. You aren't in any of those. You're trying to draw in the middle of nowhere. I can't do that, and even if I could, you wouldn't see anything."
So, what's a good way to play with drawing commands? If you want to draw in response to a button click, here's the best approach:
Make a UIGraphicsImageRenderer of appropriate size.
Call the renderer's image method.
The image method takes a closure. That's where you draw!
You now have a UIImage (returned from the call to the image method).
Make a UIImageView and set its image to that image.
Stick the UIImageView into your interface.
Example:
#IBAction func drawIt(_ sender: Any) {
let r = UIGraphicsImageRenderer(size: CGSize(width: 150, height: 150))
let im = r.image { _ in
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 46.5, y: 50.14))
bezierPath.addCurve(to: CGPoint(x: 77.5, y: 50.5), controlPoint1: CGPoint(x: 46.5, y: 50.14), controlPoint2: CGPoint(x: 64.74, y: 34.09))
bezierPath.addCurve(to: CGPoint(x: 94.5, y: 96.5), controlPoint1: CGPoint(x: 84.5, y: 59.5), controlPoint2: CGPoint(x: 88.29, y: 74.25))
UIColor.black.setStroke()
bezierPath.lineWidth = 1
bezierPath.stroke()
}
let iv = UIImageView(image:im)
iv.frame.origin = CGPoint(x: 100, y: 100)
iv.layer.borderWidth = 1
self.view.addSubview(iv)
}

Clipping the bezier path in curve shape

I using Swift to creating a custom button in a phone UI and need to clip the bottom of the button in the convex like shape.
The problem was it should be perfect and the bottom curve should match the bigger button
I tried different approaches but it's not getting the perfect result.
Using rounded corners
if buttonDirection == 0 {
path = UIBezierPath(roundedRect: rect, byRoundingCorners:[UIRectCorner.topLeft, .topRight], cornerRadii: CGSize(width: self.bounds.width/2, height: 0))
path.close()
}
Create whole path without rounded curve using QuardCurve // still bottom part not inverse curve plus corner are also not smooth as 1
if buttonDirection == 0 {
//path = UIBezierPath(roundedRect: rect, byRoundingCorners:[UIRectCorner.topLeft, .topRight], cornerRadii: CGSize(width: self.bounds.width/2, height: 0))
//path.close()
let curve = UIBezierPath()
curve.move(to: CGPoint(x: 0, y: self.bounds.height))
curve.addLine(to:CGPoint(x: 0, y: self.bounds.width/3))
curve.addQuadCurve(to: CGPoint(x: self.bounds.width, y: self.bounds.width/3), controlPoint: CGPoint(x: self.bounds.width/2, y: -5))
curve.addLine(to: CGPoint(x: self.bounds.width, y: self.bounds.height))
curve.addQuadCurve(to: CGPoint(x: 0, y: self.bounds.height), controlPoint: CGPoint(x: self.bounds.width/2, y: self.bounds.width - 10))
UIColor.black.setFill()
curve.fill()
}
Create the path from 1 and then just Clip QuardCurve
if buttonDirection == 0 {
path = UIBezierPath(roundedRect: rect, byRoundingCorners:[UIRectCorner.topLeft, .topRight], cornerRadii: CGSize(width: self.bounds.width/2, height: 0))
path.close()
path.move(to: CGPoint(x: 0, y: self.bounds.height))
path.addQuadCurve(to: CGPoint(x: self.bounds.width, y: self.bounds.height), controlPoint: CGPoint(x: self.bounds.width/2, y: self.bounds.height/2))
path.addClip()
UIColor.black.setFill()
path.fill()
}
I tried many other things also but not getting the result, What I wanted is simply clip the bottom curve from the 1st path, Like how to cut one path from another (After reading apples doc I come to know I can use path.reverse() also )
here are all buttons in one pic and when you click the button say vol + its changes the colour
PS Edited the original question, as images were confusing, and I found one solution but still want to know if there is a better solution with one path and smooth edges.
Below method worked but if someone has better solution do post.
Basically what I am doing here is first clipping the whole button with the bottom part as convex, using addLines and Quardcurve in the bottom and after that creating new path using bezier roundedRect initialization with top-right and top-left corner.
if buttonDirection == 0 {
let curve = UIBezierPath()
curve.move(to: CGPoint(x: 0, y: self.bounds.height))
curve.addLine(to: CGPoint(x: 0, y: 0))
curve.addLine(to: CGPoint(x: self.bounds.width, y: 0))
curve.addLine(to: CGPoint(x: self.bounds.width, y: self.bounds.height))
curve.addQuadCurve(to: CGPoint(x: 0, y: self.bounds.height), controlPoint: CGPoint(x: self.bounds.width/2, y: self.bounds.height - self.bounds.height/4))
curve.close()
curve.addClip()
path = UIBezierPath(roundedRect: rect, byRoundingCorners:[UIRectCorner.topLeft, .topRight], cornerRadii: CGSize(width: self.bounds.width/2, height: 0))
path.close()
}

How to set shadow at left right and bottom in UIView in ios Swift?

I want shadow at left, right and bottom in view. But Swift 3 shadow offset provide only height or width. I found similar answer see below link it show shadow top left right shadow. I want left right and bottom.
see link little similar answer
It's almost the same task as link provided, just change the BezierPath.
let block1 = UIView(frame: CGRect.init(x: 50, y: 50, width: 300, height: 300))
block1.backgroundColor = UIColor.red
block1.layer.masksToBounds = false
block1.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
block1.layer.shadowRadius = 5.0
block1.layer.shadowOpacity = 0.7
let path = UIBezierPath()
path.move(to: CGPoint(x: 0.0, y: 0.0))
// Move to center between two top points instead of bottom
path.addLine(to: CGPoint(x: block1.frame.size.width/2.0, y: block1.frame.size.height/2.0))
path.addLine(to: CGPoint(x: block1.frame.size.width, y: 0.0))
path.addLine(to: CGPoint(x: block1.frame.size.width, y: block1.frame.size.height))
path.addLine(to: CGPoint(x: 0.0, y: block1.frame.size.height))
path.close()
block1.layer.shadowPath = path.cgPath

How to make a CGRect slashed at bottom?

I'm trying to implement the following:
A custom NavigationBar which is slashed at the bottom, how can I do this using CGRect?
Another alternative would be to make a image of it and push the list up to overlap the transparent part in the image. I'd prefer the first solution if possible tho. Any suggestions?
This is my current WIP state:
You can use Bezier path
Just add lines and make your custom shape
like this:
let path = UIBezierPath()
shape.move(to: CGPoint(x: 0, y: 0))
shape.addLine(to: CGPoint(x: 0, y: 320))
shape.addLine(to: CGPoint(x: 94, y: 320))
shape.addLine(to: CGPoint(x: 64, y: 0))
shape.close()

How to create a triangle UIImage

How can I create a triangle UIImage? Here's how I'm doing it now, but it's not producing any image at all.
extension UIImage {
static func triangle(side: CGFloat, color: UIColor)->UIImage {
UIGraphicsBeginImageContextWithOptions(CGSize(width: side, height: side), false, 0)
let ctx = UIGraphicsGetCurrentContext()!
ctx.saveGState()
ctx.beginPath()
ctx.move(to: CGPoint(x: side / 2, y: 0))
ctx.move(to: CGPoint(x: side, y: side))
ctx.move(to: CGPoint(x: 0, y: side))
ctx.move(to: CGPoint(x: side / 2, y: 0))
ctx.closePath()
ctx.setFillColor(color.cgColor)
ctx.restoreGState()
let img = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return img
}
}
Your path does not contain any lines, so there's no region to fill. In addition you are not drawing the path.
Try something like this:
static func triangle(side: CGFloat, color: UIColor)->UIImage {
UIGraphicsBeginImageContextWithOptions(CGSize(width: side, height: side), false, 0)
let ctx = UIGraphicsGetCurrentContext()!
ctx.saveGState()
ctx.beginPath()
ctx.move(to: CGPoint(x: side / 2, y: 0))
//### Add lines
ctx.addLine(to: CGPoint(x: side, y: side))
ctx.addLine(to: CGPoint(x: 0, y: side))
//ctx.addLine(to: CGPoint(x: side / 2, y: 0)) //### path is automatically closed
ctx.closePath()
ctx.setFillColor(color.cgColor)
ctx.drawPath(using: .fill) //### draw the path
ctx.restoreGState()
let img = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return img
}
You might just use UIBezierPath and not use CoreGraphics at all. Furthermore, nowadays we’d use UIGraphicsImageRenderer:
extension UIImage {
static func triangle(side: CGFloat, color: UIColor) -> UIImage {
return UIGraphicsImageRenderer(size: CGSize(width: side, height: side)).image { _ in
let path = UIBezierPath()
path.move(to: CGPoint(x: side / 2, y: 0))
path.addLine(to: CGPoint(x: side, y: side))
path.addLine(to: CGPoint(x: 0, y: side))
path.close()
color.setFill()
path.fill()
}
}
}
For UIBezierPath version that still uses UIGraphicsBeginImageContextWithOptions, see previous revision of this answer.
OOPer told you about your lack of lines, and failure to actually draw the triangle.
In addition to that, I would suggest avoiding mucking around with graphics contexts. UIBezierPath makes it cleaner and easier to draw without having to worry about contexts. Also, you don't need to save and restore contexts since you are creating a temporary context and then immediately discarding it.
Here is a complete playground that draws your triangle (and fills and strokes the image rectangle so it's easier to see - you'd remove the stroke and probably fill the shape with UIColor.clear instead.)
import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
extension UIImage {
static func triangle(side: CGFloat, color: UIColor)->UIImage {
UIGraphicsBeginImageContextWithOptions(CGSize(width: side, height: side), false, 0)
//First fill the bounds with white
let rectPath = UIBezierPath(rect: CGRect(x:0, y: 0, width: side, height: side))
UIColor.white.setFill() //Use clear if you want your image to only contain the triangle.
rectPath.fill()
//Now build the triangle path
let path = UIBezierPath()
path.move(to: CGPoint(x: side / 2, y: 0))
path.addLine(to: CGPoint(x: side, y: side))
path.addLine(to: CGPoint(x: 0, y: side))
path.close() //Closing the path draws the final line
color.setFill()
path.fill()
//Stroke the outer path in gray so you can see the bounds - remove if desired
UIColor.gray.setStroke()
rectPath.stroke()
let img = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return img
}
}
let imageView = UIImageView(frame: CGRect(x:0, y: 0, width: 200, height: 200))
PlaygroundPage.current.liveView = imageView
imageView.image = UIImage.triangle(side: 200.0, color: UIColor.yellow)

Resources