Is it possible to covert CGRect into CGPathRef ? - Objective C - objective-c

I have a CGRect based on that I want to make CGPathRef.
Is it doable in objective C?

let rect = CGRect(x: 1, y: 2, width: 3, height: 4)
let path = CGPath(rect: bounds, transform: nil)

Related

PaintCode: Circle Maintains A/R?

In Paintcode 2, I have a circle inside a frame inside a canvas.
The constraints on the circle are set like this:
To get the circle to size up and not become an ellipse, I have to
Know the expected aspect ratio
Code it in Objective-C myself
Is there any way around this type of code?
-(void)drawRect:(CGRect)rect {
if (rect.size.width > rect.size.height) {
rect.origin.x = (rect.size.width - rect.size.height) * .5f;
rect.size.width = rect.size.height;
} else {
rect.origin.y = (rect.size.height - rect.size.width) * .5f;
rect.size.height = rect.size.width;
}
NSLog(#"Frame=%#", NSStringFromCGRect(rect));
[CircleDraw drawCircleWithFrame:rect];
}
Create a canvas, 150x120
Create an ellipse, 10, 10, 100, 100
Create a new variable called frame, type rectangle: 10, 10, 150, 100
Create a new expression called smallestSide: min(frame.height, frame.width)
Make an expression called position (below)
Drag smallestSide to both height and width of the ellipse
Drag the position to the position of the ellipse
position (Expression)
makePoint(
frame.x+(frame.width-smallestSide)*0.5,
frame.y+(frame.height-smallestSide)*0.5
)
output
- (void)drawCanvas1WithFrame: (CGRect)frame
{
//// Variable Declarations
CGFloat smallestSide = MIN(frame.size.height, frame.size.width);
CGPoint position = CGPointMake(frame.origin.x + (frame.size.width - smallestSide) * 0.5, frame.origin.y + (frame.size.height - smallestSide) * 0.5);
//// Oval Drawing
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(position.x, position.y, smallestSide, smallestSide)];
[UIColor.grayColor setFill];
[ovalPath fill];
}
NOTE: I had the help of Matt Dunik at PaintCode to figure this out, but the solution is actually very straightforward.

Move Back Button to the right UINavigationController

I need to move the stock back button over to the right just a few pixels as its so far to the left that when the iPad is in certain cases you cannot see the button at all.
What i've tried:
[self.navigationController.navigationItem.backBarButtonItem setImageInsets:UIEdgeInsetsMake(0, 20, 0, 0)];
[self.navigationItem.backBarButtonItem setImageInsets:UIEdgeInsetsMake(0, 20, 0, 0)];
[self.navigationController.navigationBar.backItem.backBarButtonItem setImageInsets:UIEdgeInsetsMake(0, 20, 0, 0)];
Is this possible?
For some reason, that solution isn't working in the latest versions of iOS (11+). What i made to move the back button image was an extension:
extension UIImage {
func withInsets(_ insets: UIEdgeInsets) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(
CGSize(width: size.width + insets.left + insets.right,
height: size.height + insets.top + insets.bottom),
false,
self.scale)
let origin = CGPoint(x: insets.left, y: insets.top)
self.draw(at: origin)
let imageWithInsets = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return imageWithInsets
}
}
Then, you can call your implementation like:
UINavigationBar.appearance().backIndicatorImage = UIImage(named: "icon-back")?.withInsets(UIEdgeInsets(top: 0, left: 10.0, bottom: 0, right: 0 ))

Stitch/Composite multiple images vertically and save as one image (iOS, objective-c)

I need help writing an objective-c function that will take in an array of UIImages/PNGs, and return/save one tall image of all the images stitched together in order vertically. I am new to this so please go slow and take it easy :)
My ideas so far:
Draw a UIView, then addSubviews to each one's parent (of the images)
and then ???
Below are Swift 3 and Swift 2 examples that stitch images together vertically or horizontally. They use the dimensions of the largest image in the array provided by the caller to determine the common size used for each individual frame each individual image is stitched into.
Note: The Swift 3 example preserves each image's aspect ratio, while the Swift 2 example does not. See note inline below regarding that.
UPDATE: Added Swift 3 example
Swift 3:
import UIKit
import AVFoundation
func stitchImages(images: [UIImage], isVertical: Bool) -> UIImage {
var stitchedImages : UIImage!
if images.count > 0 {
var maxWidth = CGFloat(0), maxHeight = CGFloat(0)
for image in images {
if image.size.width > maxWidth {
maxWidth = image.size.width
}
if image.size.height > maxHeight {
maxHeight = image.size.height
}
}
var totalSize : CGSize
let maxSize = CGSize(width: maxWidth, height: maxHeight)
if isVertical {
totalSize = CGSize(width: maxSize.width, height: maxSize.height * (CGFloat)(images.count))
} else {
totalSize = CGSize(width: maxSize.width * (CGFloat)(images.count), height: maxSize.height)
}
UIGraphicsBeginImageContext(totalSize)
for image in images {
let offset = (CGFloat)(images.index(of: image)!)
let rect = AVMakeRect(aspectRatio: image.size, insideRect: isVertical ?
CGRect(x: 0, y: maxSize.height * offset, width: maxSize.width, height: maxSize.height) :
CGRect(x: maxSize.width * offset, y: 0, width: maxSize.width, height: maxSize.height))
image.draw(in: rect)
}
stitchedImages = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
return stitchedImages
}
Note: The original Swift 2 example below does not preserve the
aspect ratio (e.g. in the Swift 2 example all images are expanded to
fit in the bounding box that represents the extrema of the widths and
heights of the images, thus Any non-square image can be stretched
disproportionately in one of its dimensions). If you're using Swift 2
and want to preserve the aspect ratio please use the AVMakeRect()
modification from the Swift 3 example. Since I no longer have access
to a Swift 2 playground and can't test it to ensure no errors I'm not
updating the Swift 2 example here for that.
Swift 2: (Doesn't preserve aspect ratio. Fixed in Swift 3 example above)
import UIKit
import AVFoundation
func stitchImages(images: [UIImage], isVertical: Bool) -> UIImage {
var stitchedImages : UIImage!
if images.count > 0 {
var maxWidth = CGFloat(0), maxHeight = CGFloat(0)
for image in images {
if image.size.width > maxWidth {
maxWidth = image.size.width
}
if image.size.height > maxHeight {
maxHeight = image.size.height
}
}
var totalSize : CGSize, maxSize = CGSizeMake(maxWidth, maxHeight)
if isVertical {
totalSize = CGSizeMake(maxSize.width, maxSize.height * (CGFloat)(images.count))
} else {
totalSize = CGSizeMake(maxSize.width * (CGFloat)(images.count), maxSize.height)
}
UIGraphicsBeginImageContext(totalSize)
for image in images {
var rect : CGRect, offset = (CGFloat)((images as NSArray).indexOfObject(image))
if isVertical {
rect = CGRectMake(0, maxSize.height * offset, maxSize.width, maxSize.height)
} else {
rect = CGRectMake(maxSize.width * offset, 0 , maxSize.width, maxSize.height)
}
image.drawInRect(rect)
}
stitchedImages = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
return stitchedImages
}
The normal way is to create a bitmap image context, draw your images in at the required position, and then get the image from the image context.
You can do this with UIKit, which is somewhat easier, but isn't thread safe, so will need to run in the main thread and will block the UI.
There is loads of example code around for this, but if you want to understand it properly, you should look at UIGraphicsContextBeginImageContext, UIGraphicsGetCurrentContext, UIGraphicsGetImageFromCurrentImageContext and UIImageDrawInRect. Don't forget UIGraphicsPopCurrentContext.
You can also do this with Core Graphics, which is AFAIK, safe to use on background threads ( I've not had a crash from it yet). Efficiency is about the same, as UIKit just uses CG under the hood.
Key words for this are CGBitmapContextCreate, CGContextDrawImage, CGBitmapContextCreateImage, CGContextTranslateCTM, CGContextScaleCTM and CGContextRelease ( no ARC for Core Graphics). The scaling and translating is because CG has the origin in the bottom right hand corner and Y inscreases upwards.
There is also a third way, which is to use CG for the context, but save yourself all the co-ordinate pain by using a CALayer, set your CGImage ( UIImage.CGImage) as the contents and then render the layer to the context. This is still thread safe and lets the layer take care of all the transformations. Keywords for this is - renderInContext:
I know I'm a bit late here but hopefully this can help someone out. If you're trying to create one large image out of an array you can use this method
- (UIImage *)mergeImagesFromArray: (NSArray *)imageArray {
if ([imageArray count] == 0) return nil;
UIImage *exampleImage = [imageArray firstObject];
CGSize imageSize = exampleImage.size;
CGSize finalSize = CGSizeMake(imageSize.width, imageSize.height * [imageArray count]);
UIGraphicsBeginImageContext(finalSize);
for (UIImage *image in imageArray) {
[image drawInRect: CGRectMake(0, imageSize.height * [imageArray indexOfObject: image],
imageSize.width, imageSize.height)];
}
UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return finalImage;
}
Swift 4
extension Array where Element: UIImage {
func stitchImages(isVertical: Bool) -> UIImage {
let maxWidth = self.compactMap { $0.size.width }.max()
let maxHeight = self.compactMap { $0.size.height }.max()
let maxSize = CGSize(width: maxWidth ?? 0, height: maxHeight ?? 0)
let totalSize = isVertical ?
CGSize(width: maxSize.width, height: maxSize.height * (CGFloat)(self.count))
: CGSize(width: maxSize.width * (CGFloat)(self.count), height: maxSize.height)
let renderer = UIGraphicsImageRenderer(size: totalSize)
return renderer.image { (context) in
for (index, image) in self.enumerated() {
let rect = AVMakeRect(aspectRatio: image.size, insideRect: isVertical ?
CGRect(x: 0, y: maxSize.height * CGFloat(index), width: maxSize.width, height: maxSize.height) :
CGRect(x: maxSize.width * CGFloat(index), y: 0, width: maxSize.width, height: maxSize.height))
image.draw(in: rect)
}
}
}
}
Try this piece of code,I tried stitching two images together n displayed them in an ImageView
UIImage *bottomImage = [UIImage imageNamed:#"bottom.png"]; //first image
UIImage *image = [UIImage imageNamed:#"top.png"]; //foreground image
CGSize newSize = CGSizeMake(209, 260); //size of image view
UIGraphicsBeginImageContext( newSize );
// drawing 1st image
[bottomImage drawInRect:CGRectMake(0,0,newSize.width/2,newSize.height/2)];
// drawing the 2nd image after the 1st
[image drawInRect:CGRectMake(0,newSize.height/2,newSize.width/2,newSize.height/2)] ;
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
join.image = newImage;
join is the name of the imageview and you get to see the images as a single image.
The solutions above were helpful but had a serious flaw for me. The problem is that if the images are of different sizes, the resulting stitched image would have potentially large spaces between the parts. The solution I came up with combines all images right below each other so that it looks like more like a single image no matter the individual image sizes.
For Swift 3.x
static func combineImages(images:[UIImage]) -> UIImage
{
var maxHeight:CGFloat = 0.0
var maxWidth:CGFloat = 0.0
for image in images
{
maxHeight += image.size.height
if image.size.width > maxWidth
{
maxWidth = image.size.width
}
}
let finalSize = CGSize(width: maxWidth, height: maxHeight)
UIGraphicsBeginImageContext(finalSize)
var runningHeight: CGFloat = 0.0
for image in images
{
image.draw(in: CGRect(x: 0.0, y: runningHeight, width: image.size.width, height: image.size.height))
runningHeight += image.size.height
}
let finalImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return finalImage!
}

UIView set only side borders

Is there a way to set the sides of the border of a UIView to one color and leave the top and the bottom another?
Nope—CALayer borders don’t support that behavior. The easiest way to accomplish what you want is adding an n-point-wide opaque subview with your desired border color as its background color on each side of your view.
Example:
CGSize mainViewSize = theView.bounds.size;
CGFloat borderWidth = 2;
UIColor *borderColor = [UIColor redColor];
UIView *leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, borderWidth, mainViewSize.height)];
UIView *rightView = [[UIView alloc] initWithFrame:CGRectMake(mainViewSize.width - borderWidth, 0, borderWidth, mainViewSize.height)];
leftView.opaque = YES;
rightView.opaque = YES;
leftView.backgroundColor = borderColor;
rightView.backgroundColor = borderColor;
// for bonus points, set the views' autoresizing mask so they'll stay with the edges:
leftView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleRightMargin;
rightView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin;
[theView addSubview:leftView];
[theView addSubview:rightView];
[leftView release];
[rightView release];
Note that this won’t quite match the behavior of CALayer borders—the left and right border views will always be inside the boundaries of their superview.
The answer with the views that works like borders are very nice, but remember that every view is a UI Object that cost lots of memory.
I whould use uivew's layer to paint a stroke with color on an already existing UIview.
-(CAShapeLayer*)drawLineFromPoint:(CGPoint)fromPoint toPoint:(CGPoint) toPoint withColor:(UIColor *)color andLineWidth:(CGFloat)lineWidth{
CAShapeLayer *lineShape = nil;
CGMutablePathRef linePath = nil;
linePath = CGPathCreateMutable();
lineShape = [CAShapeLayer layer];
lineShape.lineWidth = lineWidth;
lineShape.strokeColor = color.CGColor;
NSUInteger x = fromPoint.x;
NSUInteger y = fromPoint.y;
NSUInteger toX = toPoint.x;
NSUInteger toY = toPoint.y;
CGPathMoveToPoint(linePath, nil, x, y);
CGPathAddLineToPoint(linePath, nil, toX, toY);
lineShape.path = linePath;
CGPathRelease(linePath);
return lineShape;}
and add it to our view.
CAShapeLayer* borderLine=[self drawLineFromPoint:CGPointMake(0, 0) toPoint:CGPointMake(0,_myView.frame.size.height) withColor:[UIColor lightGrayColor] andLineWidth:1.0f];
[_myView.layer addSublayer:borderLine];
So... We take a point and actually painting a line from top to the bottom of our view. The result is that there is a line that looks like a one pixel width border.
Updated for Swift 3.0
I wrote a Swift extension (for a UIButton) that simulates setting a border on any side of a UIView to a given color and width. It's similar to #Noah Witherspoon's approach, but self-contained and autolayout constraint based.
// Swift 3.0
extension UIView {
enum Border {
case left
case right
case top
case bottom
}
func setBorder(border: UIView.Border, weight: CGFloat, color: UIColor ) {
let lineView = UIView()
addSubview(lineView)
lineView.backgroundColor = color
lineView.translatesAutoresizingMaskIntoConstraints = false
switch border {
case .left:
lineView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
lineView.topAnchor.constraint(equalTo: topAnchor).isActive = true
lineView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
lineView.widthAnchor.constraint(equalToConstant: weight).isActive = true
case .right:
lineView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
lineView.topAnchor.constraint(equalTo: topAnchor).isActive = true
lineView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
lineView.widthAnchor.constraint(equalToConstant: weight).isActive = true
case .top:
lineView.topAnchor.constraint(equalTo: topAnchor).isActive = true
lineView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
lineView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
lineView.heightAnchor.constraint(equalToConstant: weight).isActive = true
case .bottom:
lineView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
lineView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
lineView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
lineView.heightAnchor.constraint(equalToConstant: weight).isActive = true
}
}
}
This sounds like one of two answers:
If your view is a static size, then just put a UIView behind it that is 2 pixels wider and 2 pixels shorter than your front view.
If it is non-static sized then you could do the same, resizing the backing view whenever your foreground view is resized, or implement a custom object that implements a UIView, and implement (override) your own drawRect routine.
NAUIViewWithBorders did the trick for me. See also the creator's SO post here. Worth checking out if you need this functionality for more than a couple views.
public extension UIView {
// Border type and arbitrary tag values to identify UIView borders as subviews
public enum BorderType: Int {
case left = 20000
case right = 20001
case top = 20002
case bottom = 20003
}
public func addBorder(borderType: BorderType, width: CGFloat, color: UIColor) {
// figure out frame and resizing based on border type
var autoresizingMask: UIViewAutoresizing
var layerFrame: CGRect
switch borderType {
case .left:
layerFrame = CGRect(x: 0, y: 0, width: width, height: self.bounds.height)
autoresizingMask = [ .flexibleHeight, .flexibleRightMargin ]
case .right:
layerFrame = CGRect(x: self.bounds.width - width, y: 0, width: width, height: self.bounds.height)
autoresizingMask = [ .flexibleHeight, .flexibleLeftMargin ]
case .top:
layerFrame = CGRect(x: 0, y: 0, width: self.bounds.width, height: width)
autoresizingMask = [ .flexibleWidth, .flexibleBottomMargin ]
case .bottom:
layerFrame = CGRect(x: 0, y: self.bounds.height - width, width: self.bounds.width, height: width)
autoresizingMask = [ .flexibleWidth, .flexibleTopMargin ]
}
// look for the existing border in subviews
var newView: UIView?
for eachSubview in self.subviews {
if eachSubview.tag == borderType.rawValue {
newView = eachSubview
break
}
}
// set properties on existing view, or create a new one
if newView == nil {
newView = UIView(frame: layerFrame)
newView?.tag = borderType.rawValue
self.addSubview(newView!)
} else {
newView?.frame = layerFrame
}
newView?.backgroundColor = color
newView?.autoresizingMask = autoresizingMask
}

Cut a rectangle out of an NSBezierPath

Is it possible to remove a chunk of NSBezierPath that is defined by an NSRect region within the path?
As was noted in the comments, Mr. Caswell's answer is actually the opposite of what the OP was asking. This code sample shows how to remove a rect from a circle (or any bezier path from any other bezier path). The trick is to "reverse" the path that you want to remove, then append it to the original path:
NSBezierPath *circlePath = [NSBezierPath bezierPathWithOvalInRect:NSMakeRect(0, 0, 100, 100)];
NSBezierPath *rectPath = [NSBezierPath bezierPathWithRect:NSMakeRect(25, 25, 50, 50)];
rectPath = [rectPath bezierPathByReversingPath];
[circlePath appendBezierPath:rectPath];
Note: things get a little trickier if the bezier paths cross each other. Then you have to set the proper "winding rule".
Absolutely. This is what clipping regions do:
// Save the current clipping region
[NSGraphicsContext saveGraphicsState];
NSRect dontDrawThisRect = NSMakeRect(x, y, w, h);
// Either:
NSRectClip(dontDrawThisRect);
// Or (usually for more complex shapes):
//[[NSBezierPath bezierPathWithRect:dontDrawThisRect] addClip];
[myBezierPath fill]; // or stroke, or whatever you do
// Restore the clipping region for further drawing
[NSGraphicsContext restoreGraphicsState];
Based on Roberto's answer, I've updated the code to Swift 5. This will draw a red 100x100 circle, with a 60x60 rectangle cut out in the middle.
let path = NSBezierPath(ovalIn: NSRect(x: 0, y: 0, width: 100, height: 100))
let cutoutRect = NSBezierPath(rect: NSRect(x: 20, y: 20, width: 60, height: 60)).reversed
path.append(cutoutRect)
NSColor.red.setFill()
path.fill()