I'm trying to draw circles and in the middle of each circle, I want to draw an image.
my circles work fine but I'm not getting along with the images.
I don't understand why I can't just draw an UIImage directly.
The code below //draw PNGs is what my question is about but I posted the whole code.
thanks in advance for any help
import UIKit
import CoreGraphics
enum Shape2 {
case circle
case Rectangle
case Line
}
class Canvas: UIView {
let viewModel = ViewModel(set: set1)
var currentShape: Shape2?
override func draw(_ rect: CGRect) {
guard let currentContext = UIGraphicsGetCurrentContext() else {
print("Could not get Context")
return
}
drawIcons (user: currentContext)
}
private func drawIcons(user context: CGContext){
for i in 0...viewModel.iconsList.count-1 {
let centerPoint = CGPoint(x: viewModel.icons_coord_x[i], y: viewModel.icons_coord_y[i])
context.addArc(center: centerPoint, radius: CGFloat(viewModel.Diameters[i]), startAngle: CGFloat(0).degreesToRadians, endAngle: CGFloat(360).degreesToRadians, clockwise: true)
context.setFillColor(UIColor.blue.cgColor)
//context.setFillColor(viewModel.iconsbackground_colors[i].cgColor)
context.fillPath()
context.setLineWidth(4.0)
//draw PNGs:
let image = UIImage(named: "rocket")!
let ciImage = image.ciImage
let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
let context2 = CIContext(options: nil)
let cgImage = context2.createCGImage(ciImage ?? <#default value#>, from: ciImage?.extent ?? <#default value#>)
context.draw(CGImage() as! CGLayer, in: imageRect)
}
}
func drawShape(selectedShape: Shape2){
currentShape = selectedShape
setNeedsDisplay()
}
} ```
I don't understand why I can't just draw an UIImage directly.
You can.
https://developer.apple.com/documentation/uikit/uiimage/1624132-draw
https://developer.apple.com/documentation/uikit/uiimage/1624092-draw
Related
I'm trying to draw multiple images into a single PDFPage.
Looking at the docs and over StackOverflow seems like the best I got is to use PDFPage with an UIImage initializer like so:
let pdfPage = PDFPage(image: image)
But it just creates a page with a full-page image. I tried to draw the images using CGContext but I don't understand how to use PDFPage within a drawing context for it to draw the images rapidly like in the example below.
let bounds = page.bounds(for: .cropBox)
// Create a `UIGraphicsImageRenderer` to use for drawing an image.
let renderer = UIGraphicsImageRenderer(bounds: bounds, format: UIGraphicsImageRendererFormat.default())
let image = renderer.image { (context) in
// How do I rapidly draw them here?
}
Any help will be highly appreciated!
The result I get with PDFPage(image: <UIImage>) vs expected result:
You probably need to improve the positioning logic for the image in the loop, but this should point you to the right direction.
import UIKit
import PDFKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let pdfView = PDFView()
pdfView.frame = CGRect(x: 0, y: 50, width: view.frame.width, height: view.frame.height - 50)
if let d = createPDFDocument() {
pdfView.document = d
}
view.addSubview(pdfView)
}
func createPDFDocument() -> PDFDocument? {
let pdfDocument = PDFDocument()
let page = PDFPage()
let bounds = page.bounds(for: .cropBox)
let imageRenderer = UIGraphicsImageRenderer(bounds: bounds, format: UIGraphicsImageRendererFormat.default())
let image = imageRenderer.image { (context) in
context.cgContext.saveGState()
context.cgContext.translateBy(x: 0, y: bounds.height)
context.cgContext.concatenate(CGAffineTransform.init(scaleX: 1, y: -1))
page.draw(with: .mediaBox, to: context.cgContext)
context.cgContext.restoreGState()
// Improve logic for image position
Range(1...4).forEach { value in
let image = UIImage(named: "YOUR_IMAGE_NAME")
let rect = CGRect(x: 50 * value, y: 0, width: 40, height: 100)
image?.draw(in: rect)
}
}
let newPage = PDFPage(image: image)!
pdfDocument.insert(newPage, at: 0)
return pdfDocument
}
}
I saw few questions here on stackoverflow but none of them is solving my problem. What I want to do is to subclass NSView and draw some shapes on it. Then I want to export/save created graphics to png file. And while drawing is quite simple, I want to be able to store image with pixel precision - I know that drawing is being done in points instead of pixels. So what I am doing is I override draw() method to draw any graphic like so:
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
NSColor.white.setFill()
dirtyRect.fill()
NSColor.green.setFill()
NSColor.green.setStroke()
currentContext?.beginPath()
currentContext?.setLineWidth(1.0)
currentContext?.setStrokeColor(NSColor.green.cgColor)
currentContext?.move(to: CGPoint(x: 0, y: 0))
currentContext?.addLine(to: CGPoint(x: self.frame.width, y: self.frame.height))
currentContext?.closePath()
}
and since on screen it looks OK, after saving this to file is not what I expected. I set line width to 1 but in exported file it is 2 pixels wide. And to save image, I create NSImage from current view:
func getImage() -> NSImage? {
let size = self.bounds.size
let imageSize = NSMakeSize(size.width, size.height)
guard let imageRepresentation = self.bitmapImageRepForCachingDisplay(in: self.bounds) else {
return nil
}
imageRepresentation.size = imageSize
self.cacheDisplay(in: self.bounds, to: imageRepresentation)
let image = NSImage(size: imageSize)
image.addRepresentation(imageRepresentation)
return image
}
and this image is then save to file:
do {
guard let image = self.canvasView?.getImage() else {
return
}
let imageRep = image.representations.first as? NSBitmapImageRep
let data = imageRep?.representation(using: .png, properties: [:])
try data?.write(to: url, options: .atomic)
} catch {
print(error.localizedDescription)
}
Do you have any tips of what I am doing wrong?
I made an erase-effect on UIImageView by drawing a path with blend mode set to clear using the code below. How can I undo a given path, meaning restoring the original image under that path?
func erase(from fromPoint: CGPoint, to toPoint: CGPoint) {
UIGraphicsBeginImageContext(self.frame.size)
image?.draw(in: self.bounds)
defer {
self.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
let path = CGMutablePath()
path.move(to: fromPoint)
path.addLine(to: toPoint)
let context = UIGraphicsGetCurrentContext()!
context.setShouldAntialias(true)
context.setLineCap(.round)
context.setLineWidth(40)
context.setBlendMode(.clear)
context.addPath(path)
context.strokePath()
}
The issue is that you creating a new UIImage, discarding what was there.
Two options:
Save a copy of your old image and restore it when you want to revert.
Alternatively, don’t alter the image at all, and instead just apply a mask to the UIImageView:
extension UIImageView {
func erase(from fromPoint: CGPoint, to toPoint: CGPoint) {
let maskImage = UIGraphicsImageRenderer(bounds: bounds).image { rendererContext in
let context = rendererContext.cgContext
context.fill(bounds)
let path = CGMutablePath()
path.move(to: fromPoint)
path.addLine(to: toPoint)
context.setShouldAntialias(true)
context.setLineCap(.round)
context.setLineWidth(40)
context.setBlendMode(.clear)
context.addPath(path)
context.strokePath()
}
mask = UIImageView(image: maskImage)
}
}
Then, when you want to reverse it, just remove the mask.
This is the code I am using
extension UIImage {
var ellipseMasked: UIImage? {
guard let cgImage = cgImage else { return nil }
let rect = CGRect(origin: .zero, size: size)
return UIGraphicsImageRenderer(size: size, format: imageRendererFormat)
.image{ _ in
UIBezierPath(ovalIn: rect).addClip()
UIImage(cgImage: cgImage, scale: scale, orientation: imageOrientation)
.draw(in: rect)
}
}
}
This is the image I got
The background color is black.
How can I make the background transparent?
I tried different ways but haven't made it work yet.
You can subclass UIImageView and mask its CALayer instead of clipping the image itself:
extension CAShapeLayer {
convenience init(path: UIBezierPath) {
self.init()
self.path = path.cgPath
}
}
class EllipsedView: UIImageView {
override func layoutSubviews() {
super.layoutSubviews()
layer.mask = CAShapeLayer(path: .init(ovalIn: bounds))
}
}
let profilePicture = UIImage(data: try! Data(contentsOf: URL(string:"http://i.stack.imgur.com/Xs4RX.jpg")!))!
let iv = EllipsedView(image: profilePicture)
edit/update
If you need to clip the UIImage itself you can do it as follow:
extension UIImage {
var ellipseMasked: UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, scale)
defer { UIGraphicsEndImageContext() }
UIBezierPath(ovalIn: .init(origin: .zero, size: size)).addClip()
draw(in: .init(origin: .zero, size: size))
return UIGraphicsGetImageFromCurrentImageContext()
}
}
For iOS10+ you can use UIGraphicsImageRenderer.
extension UIImage {
var ellipseMasked: UIImage {
let rect = CGRect(origin: .zero, size: size)
let format = imageRendererFormat
format.opaque = false
return UIGraphicsImageRenderer(size: size, format: format).image{ _ in
UIBezierPath(ovalIn: rect).addClip()
draw(in: rect)
}
}
}
let profilePicture = UIImage(data: try! Data(contentsOf: URL(string:"http://i.stack.imgur.com/Xs4RX.jpg")!))!
profilePicture.ellipseMasked
Here are two solutions using SwiftUI.
This solution can be used to clip the image shape to a circle.
Image("imagename").resizable()
.clipShape(Circle())
.scaledToFit()
This solution can be used to get more of an eclipse or oval shape from the image.
Image("imagename").resizable()
.cornerRadius(100)
.scaledToFit()
.padding()
I've been trawling the internet for days trying to find the simplest code examples on how to draw a rectangle or lines procedurally in Swift. I have seen how to do it by overriding the DrawRect command. I believe you can create a CGContext and then drawing into an image, but I'd love to see some simple code examples. Or is this a terrible approach? Thanks.
class MenuController: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = UIColor.blackColor()
var logoFrame = CGRectMake(0,0,118,40)
var imageView = UIImageView(frame: logoFrame)
imageView.image = UIImage(named:"Logo")
self.view.addSubview(imageView)
//need to draw a rectangle here
}
}
Here's an example that creates a custom UIImage containing a transparent background and a red rectangle with lines crossing diagonally through it.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad();
let imageSize = CGSize(width: 200, height: 200)
let imageView = UIImageView(frame: CGRect(origin: CGPoint(x: 100, y: 100), size: imageSize))
self.view.addSubview(imageView)
let image = drawCustomImage(size: imageSize)
imageView.image = image
}
}
func drawCustomImage(size: CGSize) -> UIImage {
// Setup our context
let bounds = CGRect(origin: .zero, size: size)
let opaque = false
let scale: CGFloat = 0
UIGraphicsBeginImageContextWithOptions(size, opaque, scale)
let context = UIGraphicsGetCurrentContext()!
// Setup complete, do drawing here
context.setStrokeColor(UIColor.red.cgColor)
context.setLineWidth(2)
context.stroke(bounds)
context.beginPath()
context.move(to: CGPoint(x: bounds.minX, y: bounds.minY))
context.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY))
context.move(to: CGPoint(x: bounds.maxX, y: bounds.minY))
context.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
context.strokePath()
// Drawing complete, retrieve the finished image and cleanup
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
An updated answer using Swift 3.0
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad();
let imageSize = CGSize(width: 200, height: 200)
let imageView = UIImageView(frame: CGRect(origin: CGPoint(x: 100, y: 100), size: imageSize))
self.view.addSubview(imageView)
let image = drawCustomImage(size: imageSize)
imageView.image = image
}
}
func drawCustomImage(size: CGSize) -> UIImage? {
// Setup our context
let bounds = CGRect(origin: CGPoint.zero, size: size)
let opaque = false
let scale: CGFloat = 0
UIGraphicsBeginImageContextWithOptions(size, opaque, scale)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
// Setup complete, do drawing here
context.setStrokeColor(UIColor.red.cgColor)
context.setLineWidth(5.0)
// Would draw a border around the rectangle
// context.stroke(bounds)
context.beginPath()
context.move(to: CGPoint(x: bounds.maxX, y: bounds.minY))
context.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
context.strokePath()
// Drawing complete, retrieve the finished image and cleanup
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
let imageSize = CGSize(width: 200, height: 200)
let imageView = UIImageView(frame: CGRect(origin: CGPoint(x: 100, y: 100), size: imageSize))
let image = drawCustomImage(size: imageSize)
imageView.image = image
I used the accepted answer to draw lines in a Tic Tac Toe game when one of the players won. Thanks, good to know that it worked. Unfortunately, I ran into some problems getting it to work on different sizes of iPhones and iPads simultaneously. That's probably something that should have been addressed. Basically what I'm saying is that it might not actually be worth the trouble of all that code, depending on your case.
My alternate solution is to simply make customized, better looking line in Photoshop and then load it with UIImageView. For me this was MUCH simpler, runs better, and looks better. Obviously it really depends on what you need it for.
Steps:
1: Download or create an image (preferably saved as .PNG)
2: Drag it into your project
3: Drag a UIImage View into your storyboard
4: Click on the Image View and select the image in the attributes inspector
5: Ctrl click and drag the Image View to your .swift file to declare an Outlet
6: Set the autolayout constraints so it works on ALL devices EASILY
Animating, rotating, and transforming image views on and off the screen is also arguably easier
To change the image:
yourImageViewOutletName.image = UIImage(named: "ImageNameHere")