how is the variable filename here changing? - objective-c

For some reason, my variable filename changes by the time it gets to setupView to filename from a more recent instance of the class and i cant figure out why. I just started learning obj-C, so im assuming im doing some sort of rookie mistake.
They are instantiated by:
campusMapTileZero=[[CampusMapTile alloc] initNameOfTiletoLoad:#"cm0"];
campusMapTileOne=[[CampusMapTile alloc] initNameOfTiletoLoad:#"cm1"];
and the implementation im referring to is:
#import "CampusMapTile.h"
#import "TileManager.h"
#import "GLView.h"
#import "OpenGLCommon.h"
#import "ConstantsAndMacros.h"
#implementation CampusMapTile
NSString *filename;
- (id) initNameOfTiletoLoad: (NSString *)filename
{
if(self = [super init]) {
NSLog(#"initialized map tile for tile:");
NSLog(filename);
[self setTile:filename];
}
return self;
}
-(void)setTile:(NSString*) _filename {
NSLog(#"set tile called from setTile.");
filename=_filename;
NSLog(#"filename in setTile is:");
NSLog(filename);
}
- (void)drawView:(GLView*)view
{
glBindTexture(GL_TEXTURE_2D, texture[0]);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
static const Vertex3D vertices[] = {
{0,0, 1}, //TL
{ 1024,0, 1}, //TR
{0,-1024, 1}, //BL
{ 1024.0f, -1024.0f, 1} //BR
};
static const GLfloat texCoords[] = {
0.0, 1.0,
1.0, 1.0,
0.0, 0.0,
1.0, 0.0
};
glVertexPointer(3, GL_FLOAT, 0, vertices);
glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
- (void)setupView:(GLView*)view {
NSLog(#"setup view called");
NSLog(filename);
[self loadTextures:filename textureIdentifier:0];
}
-(void)loadTextures:(NSString*) filename textureIdentifier:(int) textureNumber {
//enable textures.
glEnable(GL_TEXTURE_2D);
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_FASTEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Bind the number of textures we need.
glGenTextures(1, &texture[textureNumber]);
glBindTexture(GL_TEXTURE_2D, texture[textureNumber]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
// [tileManager setupView:view];
NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:#"jpg"];
NSData *texData = [[NSData alloc] initWithContentsOfFile:path];
UIImage *image = [[UIImage alloc] initWithData:texData];
if (image == nil)
NSLog(#"Do real error checking here");
GLuint width = CGImageGetWidth(image.CGImage);
GLuint height = CGImageGetHeight(image.CGImage);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
void *imageData = malloc( height * width * 4 );
CGContextRef context = CGBitmapContextCreate( imageData, width, height, 8, 4 * width, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big );
// Flip the Y-axis
CGContextTranslateCTM (context, 0, height);
CGContextScaleCTM (context, 1.0, -1.0);
CGColorSpaceRelease( colorSpace );
CGContextClearRect( context, CGRectMake( 0, 0, width, height ) );
CGContextDrawImage( context, CGRectMake( 0, 0, width, height ), image.CGImage );
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
CGContextRelease(context);
free(imageData);
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView
{
}
*/
/*
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
}
*/
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end

The problem is that your filename variable is a global variable, not an instance variable.
You need to delete your existing NSString* filename declaration and instead declare filename in your class's #interface block, like so:
#interface CampusMapTile : UIViewController {
NSString* filename;
}

in setTile you're just assigning pointers so if the string that the pointer is point to changes, the filename you store in the class instance will also change. try something like:
-(void)setTile:(NSString*) _filename {
NSLog(#"set tile called from setTile.");
filename = [_filename copy];
NSLog(#"filename in setTile is:");
NSLog(filename);
}

Related

Exporting a QR to vector NSImage & CGPDFContextCreate - macOS ObjC

I've been trying to figure out how to export my QR codes on my little project as a vector.
I start from qrImageForString function which generates the image initially, and does so as a bitmap.
The main two lines in question are:
CGContextRef ctx = CGBitmapContextCreate(0, size, size, 8, size * 4, colorSpace,kCGImageAlphaPremultipliedLast);
CGImageRef qrCGImage = CGBitmapContextCreateImage(ctx);
For the first one I think this may be a replacement:
NSRect rect = CGRectMake(0.0, 0.0, 750.0, 750.0);
CGContextRef ctx = CGPDFContextCreate(dataConsumer, &rect, NULL);
However I'm not sure how to replace the second line to make it a vector.
Full function is:
+ (NSImage *)qrImageForString:(NSString *)string imageSize:(CGFloat)size {
if (![string length]) {
return nil;
}
int str1;
QRcode *code = QRcode_encodeString([string UTF8String], 0, str1, QR_MODE_8, 1);
// create context
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef ctx = CGBitmapContextCreate(0, size, size, 8, size * 4, colorSpace, kCGImageAlphaPremultipliedLast);
NSRect rect = CGRectMake(0.0, 0.0, 750.0, 750.0);
NSMutableData* outputData = [[NSMutableData alloc] init];
CGDataConsumerRef dataConsumer = CGDataConsumerCreateWithCFData((CFMutableDataRef)outputData);
CGAffineTransform translateTransform = CGAffineTransformMakeTranslation(0, -size);
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(1, -1);
CGContextConcatCTM(ctx, CGAffineTransformConcat(translateTransform, scaleTransform));
// draw QR on this context
[MainClass drawQRCode:code context:ctx size:size];
// get image
CGImageRef qrCGImage = CGBitmapContextCreateImage(ctx);
NSImage *qrImage = [[NSImage alloc] initWithCGImage:qrCGImage size:NSZeroSize];
// some releases
CGContextRelease(ctx);
CGImageRelease(qrCGImage);
CGColorSpaceRelease(colorSpace);
QRcode_free(code);
return qrImage;
}
Copied from How to make dataWithEPSInsideRect vector rather than bitmap in vector format?: Create an offscreen view, set up the draw method of the view to include your graphic and then use NSView's dataWithEPSInsideRect: method.
I made mdDrawQRCode:context:size:fillColor: an instance method of the view but it can be a method of different class. The view:
#interface MyView : NSView
#property (strong) NSString *qrString;
#end
#implementation MyView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
if (!self.qrString)
return;
CGContextRef cgContext = [[NSGraphicsContext currentContext] CGContext];
CGContextSaveGState(cgContext);
QRcode *code = QRcode_encodeString([self.qrString UTF8String], 0, QR_ECLEVEL_L, QR_MODE_8, 1);
[self mdDrawQRCode:code context:cgContext size:self.bounds.size.width fillColor:[NSColor blackColor]];
QRcode_free(code);
CGContextRestoreGState(cgContext);
}
#end
Use the view like:
MyView *myView = [[MyView alloc] initWithFrame:NSMakeRect(0, 0, 100.0, 100.0)];
myView.qrString = #"Hello World!";
NSData *data = [myView dataWithEPSInsideRect:myView.bounds];

How to fix the translation problem when drawing a Label with Rotation

I have a View with some subviews on it like Labels and ImageViews. The subviews can have any transformation. I have to convert the View into PDF using CGContext. The PDF is created but there is issue in label positions in final PDF file when I rotate them in View. I don't know where I am doing wrong. I am using a Category to Draw hierarchy.
Below is my code and Source view and final PDF.
UIGraphicsBeginPDFContextToFile(myPathDocs, CGRectMake(0, 0, 612, 792), nil);
// Mark the beginning of a new page.
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(),bgColor.CGColor);
CGContextFillRect(UIGraphicsGetCurrentContext(),CGRectMake(0, 0, 612, 792));
[_pdfView drawHierarchy];
UIGraphicsEndPDFContext();
// Category
#import "UIView+HierarchicalDrawing.h"
#implementation UIView (HierarchicalDrawing)
- (void)drawHierarchy {
[self layoutSubviews];
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSaveGState(c);
CGContextTranslateCTM(c, self.frame.origin.x, self.frame.origin.y);
if (self.backgroundColor != nil) {
CGContextSetFillColorWithColor(c, [self.backgroundColor CGColor]);
CGContextFillRect(c, self.bounds);
}
if ([self isKindOfClass:[UIImageView class]]) {
if(self.layer.shadowColor) {
CGContextSetShadowWithColor(c, self.layer.shadowOffset, 0.0, self.layer.shadowColor);
}
}
if ([self isKindOfClass:[UILabel class]]) {
CGPoint centrePoint = self.center;
CGFloat angle = atan2f(self.transform.b, self.transform.a);
CGContextRotateCTM(c, angle);
}
[self drawRect:self.bounds];
if(self.clipsToBounds) CGContextClipToRect(c, self.bounds);
for(UIView *v in self.subviews) {
if(v.hidden) continue;
[v drawHierarchy];
}
// NSLog(#"counter is %d",counter);
CGContextRestoreGState(c);
}
#end

Saving UIImageView and overlayed UIView as an image (xcode/iOS)

I have an image loaded into a UIImageView, which has a UIView overlayed (with a CGRect drawn inside it), I am looking for a way to save what is displayed on the screen as a new image. I am using storyboards and ARC.
I have a UIViewController, which contains UIImageView. The UIView is displayed on top of this, and a circle is drawn where the user touches the screen. This all works fine, but now I want to save what is displayed as an image (JPG).
Screenshot of my storyboard:
Below is the code from my PhotoEditViewController so far. passedPhoto is the photo that is loaded into the UIImageView.
#import "PhotoEditViewController.h"
#interface PhotoEditViewController ()
#end
#implementation PhotoEditViewController
#synthesize selectedPhoto = _selectedPhoto;
#synthesize backButton;
#synthesize savePhoto;
- (void)viewDidLoad {
[super viewDidLoad];
[passedPhoto setImage:_selectedPhoto];
}
- (void)viewDidUnload {
[self setBackButton:nil];
[self setSavePhoto:nil];
[super viewDidUnload];
}
- (IBAction)savePhoto:(id)sender{
NSLog(#"save photo");
// this is where it needs to happen!
}
#end
Below is the code from my PhotoEditView which handles the creation of the circle overlay:
#import "PhotoEditView.h"
#implementation PhotoEditView
#synthesize myPoint = _myPoint;
- (void)setMyPoint:(CGPoint)myPoint {
_myPoint = myPoint;
self.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.01];
[self setNeedsDisplay];
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
return self;
}
-(void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event {
NSSet *allTouches = [event allTouches];
switch ([allTouches count]){
case 1: {
UITouch *touch = [[allTouches allObjects] objectAtIndex:0];
CGPoint point = [touch locationInView:self];
NSLog(#"x=%f", point.x);
NSLog(#"y=%f", point.y);
self.myPoint = point;
}
break;
}
}
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ctx, 4.0);
CGContextSetRGBFillColor(ctx, 0, 0, 0, 0.5);
CGContextSetRGBStrokeColor(ctx, 1.0, 0.0, 0.0, 1);
CGContextStrokePath(ctx);
CGRect circlePoint = CGRectMake(self.myPoint.x - 50, self.myPoint.y - 50, 100.0, 100.0);
CGContextStrokeEllipseInRect(ctx, circlePoint);
}
#end
Any help would be appreciated.
Have you tried this:
UIGraphicsBeginImageContext(rect.size);
[imageView.layer renderInContext:UIGraphicsGetCurrentContext()];
[customView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *imgView = [[UIImageView alloc] initWithImage:viewImage];
This is just the same approach as for getting an UIImage from a UIView, only you draw two views in the same context.

drawRect drawing 'transparent' text?

I am looking to draw a UILabel (preferable through subclassing) as a transparent label, but with solid background. I draw up an quick example (sorry, it's ugly, but it gets the points across :)).
Basically I have a UILabel and I would like the background to be a set colour, and the text should be transparent. I do not want to colour the text with the views background, but instead have it be 100% transparent, since I have a texture in the background that I want to make sure lines up inside and outside of the label.
I've been spending the night browsing SO and searching on Google, but I have found no helpful sources. I don't have much experience with CG drawing, so I would appreciate any links, help, tutorial or sample code (maybe Apple has some I need to have a look at?).
Thanks a bunch!
I've rewritten it as a UILabel subclass using barely any code and posted it on GitHub
The gist of it is you override drawRect but call [super drawRect:rect] to let the UILabel render as normal. Using a white label color lets you easily use the label itself as a mask.
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
// let the superclass draw the label normally
[super drawRect:rect];
CGContextConcatCTM(context, CGAffineTransformMake(1, 0, 0, -1, 0, CGRectGetHeight(rect)));
// create a mask from the normally rendered text
CGImageRef image = CGBitmapContextCreateImage(context);
CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(image), CGImageGetHeight(image), CGImageGetBitsPerComponent(image), CGImageGetBitsPerPixel(image), CGImageGetBytesPerRow(image), CGImageGetDataProvider(image), CGImageGetDecode(image), CGImageGetShouldInterpolate(image));
CFRelease(image); image = NULL;
// wipe the slate clean
CGContextClearRect(context, rect);
CGContextSaveGState(context);
CGContextClipToMask(context, rect, mask);
CFRelease(mask); mask = NULL;
[self RS_drawBackgroundInRect:rect];
CGContextRestoreGState(context);
}
Solved using CALayer masks. Creating a standard mask (wallpapered text, for example) is simple. To create the knocked-out text, I had to invert the alpha channel of my mask, which involved rendering a label to a CGImageRef and then doing some pixel-pushing.
Sample application is available here: https://github.com/robinsenior/RSMaskedLabel
Relevant code is here to avoid future link-rot:
#import "RSMaskedLabel.h"
#import <QuartzCore/QuartzCore.h>
#interface UIImage (RSAdditions)
+ (UIImage *) imageWithView:(UIView *)view;
- (UIImage *) invertAlpha;
#end
#interface RSMaskedLabel ()
{
CGImageRef invertedAlphaImage;
}
#property (nonatomic, retain) UILabel *knockoutLabel;
#property (nonatomic, retain) CALayer *textLayer;
- (void) RS_commonInit;
#end
#implementation RSMaskedLabel
#synthesize knockoutLabel, textLayer;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[self RS_commonInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self RS_commonInit];
}
return self;
}
+ (Class)layerClass
{
return [CAGradientLayer class];
}
- (void) RS_commonInit
{
[self setBackgroundColor:[UIColor clearColor]];
// create the UILabel for the text
knockoutLabel = [[UILabel alloc] initWithFrame:[self frame]];
[knockoutLabel setText:#"booyah"];
[knockoutLabel setTextAlignment:UITextAlignmentCenter];
[knockoutLabel setFont:[UIFont boldSystemFontOfSize:72.0]];
[knockoutLabel setNumberOfLines:1];
[knockoutLabel setBackgroundColor:[UIColor clearColor]];
[knockoutLabel setTextColor:[UIColor whiteColor]];
// create our filled area (in this case a gradient)
NSArray *colors = [[NSArray arrayWithObjects:
(id)[[UIColor colorWithRed:0.349 green:0.365 blue:0.376 alpha:1.000] CGColor],
(id)[[UIColor colorWithRed:0.455 green:0.490 blue:0.518 alpha:1.000] CGColor],
(id)[[UIColor colorWithRed:0.412 green:0.427 blue:0.439 alpha:1.000] CGColor],
(id)[[UIColor colorWithRed:0.208 green:0.224 blue:0.235 alpha:1.000] CGColor],
nil] retain];
NSArray *gradientLocations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.54],
[NSNumber numberWithFloat:0.55],
[NSNumber numberWithFloat:1], nil];
// render our label to a UIImage
// if you remove the call to invertAlpha it will mask the text
invertedAlphaImage = [[[UIImage imageWithView:knockoutLabel] invertAlpha] CGImage];
// create a new CALayer to use as the mask
textLayer = [CALayer layer];
// stick the image in the layer
[textLayer setContents:(id)invertedAlphaImage];
// create a nice gradient layer to use as our fill
CAGradientLayer *gradientLayer = (CAGradientLayer *)[self layer];
[gradientLayer setBackgroundColor:[[UIColor clearColor] CGColor]];
[gradientLayer setColors: colors];
[gradientLayer setLocations:gradientLocations];
[gradientLayer setStartPoint:CGPointMake(0.0, 0.0)];
[gradientLayer setEndPoint:CGPointMake(0.0, 1.0)];
[gradientLayer setCornerRadius:10];
// mask the text layer onto our gradient
[gradientLayer setMask:textLayer];
}
- (void)layoutSubviews
{
// resize the text layer
[textLayer setFrame:[self bounds]];
}
- (void)dealloc
{
CGImageRelease(invertedAlphaImage);
[knockoutLabel release];
[textLayer release];
[super dealloc];
}
#end
#implementation UIImage (RSAdditions)
/*
create a UIImage from a UIView
*/
+ (UIImage *) imageWithView:(UIView *)view
{
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0.0);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
/*
get the image to invert its alpha channel
*/
- (UIImage *)invertAlpha
{
// scale is needed for retina devices
CGFloat scale = [self scale];
CGSize size = self.size;
int width = size.width * scale;
int height = size.height * scale;
CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *memoryPool = (unsigned char *)calloc(width*height*4, 1);
CGContextRef context = CGBitmapContextCreate(memoryPool, width, height, 8, width * 4, colourSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colourSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), [self CGImage]);
for(int y = 0; y < height; y++)
{
unsigned char *linePointer = &memoryPool[y * width * 4];
for(int x = 0; x < width; x++)
{
linePointer[3] = 255-linePointer[3];
linePointer += 4;
}
}
// get a CG image from the context, wrap that into a
CGImageRef cgImage = CGBitmapContextCreateImage(context);
UIImage *returnImage = [UIImage imageWithCGImage:cgImage scale:scale orientation:UIImageOrientationUp];
// clean up
CGImageRelease(cgImage);
CGContextRelease(context);
free(memoryPool);
// and return
return returnImage;
}
#end
Here's a technique that's similar to Matt Gallagher's, which will generate an inverted text mask with an image.
Allocate a (mutable) data buffer. Create a bitmap context with an 8-bit alpha channel. Configure settings for text drawing. Fill the whole buffer in copy mode (default colour assumed to have alpha value of 1). Write the text in clear mode (alpha value of 0). Create an image from the bitmap context. Use the bitmap as a mask to make a new image from the source image. Create a new UIImage and clean up.
Every time the textString or sourceImage or size values change, re-generate the final image.
CGSize size = /* assume this exists */;
UIImage *sourceImage = /* assume this exists */;
NSString *textString = /* assume this exists */;
char *text = [textString cStringUsingEncoding:NSMacOSRomanStringEncoding];
NSUInteger len = [textString lengthOfBytesUsingEncoding:cStringUsingEncoding:NSMacOSRomanStringEncoding];
NSMutableData *data = [NSMutableData dataWithLength:size.width*size.height*1];
CGContextRef context = CGBitmapContextCreate([data mutableBytes], size.width, size.height, 8, size.width, NULL, kCGImageAlphaOnly);
CGContextSelectFont(context, "Gill Sans Bold", 64.0f, kCGEncodingMacRoman);
CGContextSetTextDrawingMode(context, kCGTextFill);
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextFillRect(context, overlay.bounds);
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextShowTextAtPoint(context, 16.0f, 16.0f, text, len);
CGImageRef textImage = CGBitmapContextCreateImage(context);
CGImageRef newImage = CGImageCreateWithMask(sourceImage.CGImage, textImage);
UIImage *finalImage = [UIImage imageWithCGImage:newImage];
CGContextRelease(context);
CFRelease(newImage);
CFRelease(textImage);
Another way to do this involves putting the textImage into a new layer and setting that layer on your view's layer. (Remove the lines that create "newImage" and "finalImage".) Assuming this happens inside your view's code somewhere:
CALayer *maskLayer = [[CALayer alloc] init];
CGPoint position = CGPointZero;
// layout the new layer
position = overlay.layer.position;
position.y *= 0.5f;
maskLayer.bounds = overlay.layer.bounds;
maskLayer.position = position;
maskLayer.contents = (__bridge id)textImage;
self.layer.mask = maskLayer;
There are more alternatives, some might be better (subclass UIImage and draw the text directly in clear mode after the superclass has done its drawing?).
Swift 5 solution (Xcode: 12.5):
class MaskedLabel: UILabel {
var maskColor : UIColor?
override init(frame: CGRect) {
super.init(frame: frame)
customInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
customInit()
}
func customInit() {
maskColor = self.backgroundColor
self.textColor = UIColor.white
backgroundColor = UIColor.clear
self.isOpaque = false
}
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
super.draw(rect)
context.concatenate(__CGAffineTransformMake(1, 0, 0, -1, 0, rect.height))
let image: CGImage = context.makeImage()!
let mask: CGImage = CGImage(maskWidth: image.width, height: image.height, bitsPerComponent: image.bitsPerComponent, bitsPerPixel: image.bitsPerPixel, bytesPerRow: image.bytesPerRow, provider: image.dataProvider!, decode: image.decode, shouldInterpolate: image.shouldInterpolate)!
context.clear(rect)
context.saveGState()
context.clip(to: rect, mask: mask)
if (self.layer.cornerRadius != 0.0) {
context.addPath(CGPath(roundedRect: rect, cornerWidth: self.layer.cornerRadius, cornerHeight: self.layer.cornerRadius, transform: nil))
context.clip()
}
drawBackgroundInRect(rect: rect)
context.restoreGState()
}
func drawBackgroundInRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
if let _ = maskColor {
maskColor!.set()
}
context!.fill(rect)
}
}

How to create a CGGradient for my UIView subclass?

Does anyone know how to create a CGGradient that will fill my view,
my current code is this and it fill the UIView with a red rectangle I want to have a gradient (from black to grey for instance) instead of the rect :
- (void)drawRect:(CGRect)rect {
CGContextRef context=UIGraphicsGetCurrentContext();
CGRect r;
r.origin.x=0.;
r.origin.y=0.;
r.size.width=rect.size.width;
r.size.height=rect.size.height;
CGContextSetRGBFillColor(context, 1., 0., 0., 1.);
CGContextFillRect (context,r);
}
In my answer to this question, I provide code for drawing a gloss gradient within a UIView. The colors and drawing positions can be modified from that to form whatever linear gradient you need.
this is a subclass where you can chose the colors
.h file
#import <UIKit/UIKit.h>
#interface GradientView : UIView {
CGGradientRef gradient;
}
#property(nonatomic, assign) CGGradientRef gradient;
- (id)initWithGradient:(CGGradientRef)gradient;
- (id)initWithColor:(UIColor*)top bottom:(UIColor*)bot;
- (void)setGradientWithColor:(UIColor*)top bottom:(UIColor*)bot;
- (void)getRGBA:(CGFloat*)buffer;
#end
.m file
#import "GradientView.h"
#implementation GradientView
#synthesize gradient;
- (id)initWithGradient:(CGGradientRef)grad {
self = [super init];
if(self){
[self setGradient:grad];
}
return self;
}
- (id)initWithColor:(UIColor*)top bottom:(UIColor*)bot {
self = [super init];
if(self){
[self setGradientWithColor:top bottom:bot];
}
return self;
}
- (void)setGradient:(CGGradientRef)g {
if(gradient != NULL && g != gradient){
CGGradientRelease(gradient);
}
if(g != gradient){
CGGradientRetain(g);
}
gradient = g;
[self setNeedsDisplay];
}
- (void)setGradientWithColor:(UIColor*)top bottom:(UIColor*)bot {
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
CGFloat clr[8];
[top getRGBA:clr];
[bot getRGBA:clr+4] ;
CGGradientRef grad = CGGradientCreateWithColorComponents(rgb, clr, NULL, sizeof(clr)/(sizeof(clr[0])*4));
[self setGradient:grad];
CGColorSpaceRelease(rgb);
CGGradientRelease(grad);
}
- (void)getRGBA:(CGFloat*)buffer {
CGColorRef clr = [self CGColor];
NSInteger n = CGColorGetNumberOfComponents(clr);
const CGFloat *colors = CGColorGetComponents(clr);
// TODO: add other ColorSpaces support
switch (n) {
case 2:
for(int i = 0; i<3; ++i){
buffer[i] = colors[0];
}
buffer[3] = CGColorGetAlpha(clr);
break;
case 3:
for(int i = 0; i<3; ++i){
buffer[i] = colors[i];
}
buffer[3] = 1.0;
break;
case 4:
for(int i = 0; i<4; ++i){
buffer[i] = colors[i];
}
break;
default:
break;
}
}
- (void)drawRect:(CGRect)rect {
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextDrawLinearGradient(c, gradient, CGPointMake(0, 0), CGPointMake(0, rect.size.height), 0);
}
- (void)dealloc {
CGGradientRelease(gradient);
[super dealloc];
}
#end
Use CGGradientCreateWithColorComponents or CGGradientCreateWithColors to create the gradient. (The latter takes CGColor objects.) Then, use CGContextDrawLinearGradient or CGContextDrawRadialGradient to draw it.
A linear gradient will extend infinitely in at least the two directions perpendicular to the line of the gradient. A radial gradient extends infinitely in every direction. To prevent the gradient from spilling outside your view, you'll probably need to add the view's bounds to the clipping path using CGContextClipToRect.