loading screen not centered on second launch - objective-c

I have a UIView which is my loading view. All it does is display the circular loading circle(lol to much "circle" for one sentence).
It works fine the first time but after that the circle is not centered. It moves to the left and down some. How can I get it to always be centered, take in mind I have limited the app to only display in the landscape modes (landscape left, landscape right) in all views so the issue is not coming from the device being rotated.
call to load the view:
loadingViewController = [LoadingViewController loadSpinnerIntoView:self.view];
LoadingViewController.h:
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import "CrestronClient.h"
#interface LoadingViewController : UIView
{
CrestronClient *cClient;
}
+(LoadingViewController *)loadSpinnerIntoView:(UIView *)superView;
-(void)removeLoadingView;
- (UIImage *)addBackground;
#end
LoadingView.m:
#import "LoadingViewController.h"
#import "RootViewController.h"
#implementation LoadingViewController
CGRect priorFrameSettings;
UIView *parentView;
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
if (interfaceOrientation == UIInterfaceOrientationLandscapeLeft ||interfaceOrientation == UIInterfaceOrientationLandscapeRight ) {
return YES;
}else{
return NO;
}
}
-(void)removeLoadingView
{
// [parentView setFrame:priorFrameSettings];
CATransition *animation = [CATransition animation];
[animation setType:kCATransitionFade];
[[[self superview] layer] addAnimation:animation forKey:#"layerAnimation"];
[self removeFromSuperview];
}
+(LoadingViewController *)loadSpinnerIntoView:(UIView *)superView
{
priorFrameSettings = superView.frame;
parentView = superView;
// [superView setFrame:CGRectMake(0, 0, 1024, 1024)];
// Create a new view with the same frame size as the superView
LoadingViewController *loadingViewController = [[LoadingViewController alloc] initWithFrame:superView.frame];
loadingViewController.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
// If something's gone wrong, abort!
if(!loadingViewController){ return nil; }
[superView addSubview:loadingViewController];
if(!loadingViewController){ return nil; }
// This is the new stuff here ;)
UIActivityIndicatorView *indicator =
[[[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle: UIActivityIndicatorViewStyleWhiteLarge] autorelease];
// Set the resizing mask so it's not stretched
UIImageView *background = [[UIImageView alloc] initWithImage:[loadingViewController addBackground]];
// Make a little bit of the superView show through
background.alpha = 0.7;
[loadingViewController addSubview:background];
indicator.autoresizingMask =
UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleRightMargin |
UIViewAutoresizingFlexibleBottomMargin |
UIViewAutoresizingFlexibleLeftMargin;
// Place it in the middle of the view
indicator.center = superView.center;
// Add it into the spinnerView
[loadingViewController addSubview:indicator];
// Start it spinning! Don't miss this step
[indicator startAnimating];
// Create a new animation
CATransition *animation = [CATransition animation];
// Set the type to a nice wee fade
[animation setType:kCATransitionFade];
// Add it to the superView
[[superView layer] addAnimation:animation forKey:#"layerAnimation"];
return loadingViewController;
}
- (UIImage *)addBackground{
cClient = [CrestronClient sharedManager];
if (cClient.isConnected == FALSE) {
[cClient connect];
}
// Create an image context (think of this as a canvas for our masterpiece) the same size as the view
UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 1);
// Our gradient only has two locations - start and finish. More complex gradients might have more colours
size_t num_locations = 2;
// The location of the colors is at the start and end
CGFloat locations[2] = { 0.0, 1.0 };
// These are the colors! That's two RBGA values
CGFloat components[8] = {
0.4,0.4,0.4, 0.8,
0.1,0.1,0.1, 0.5 };
// Create a color space
CGColorSpaceRef myColorspace = CGColorSpaceCreateDeviceRGB();
// Create a gradient with the values we've set up
CGGradientRef myGradient = CGGradientCreateWithColorComponents (myColorspace, components, locations, num_locations);
// Set the radius to a nice size, 80% of the width. You can adjust this
float myRadius = (self.bounds.size.width*.8)/2;
// Now we draw the gradient into the context. Think painting onto the canvas
CGContextDrawRadialGradient (UIGraphicsGetCurrentContext(), myGradient, self.center, 0, self.center, myRadius, kCGGradientDrawsAfterEndLocation);
// Rip the 'canvas' into a UIImage object
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// And release memory
CGColorSpaceRelease(myColorspace);
CGGradientRelease(myGradient);
UIGraphicsEndImageContext();
// … obvious.
return image;
}
- (void)dealloc {
[super dealloc];
}
#end

Make sure the loading view is set to its parents frame and has the proper autoresizingMask set. This would likely by UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight.

fixed the background by adding
[background setFrame:CGRectMake(0, 0, 1024, 768 )];
and fixed the centering of the circle with:
indicator.center = background.center;

Related

How to display animated GIF in Objective C on top of the layered View?

I am trying to draw animated gif on my screen in mac OSX app .
I used this code to insert the gif: I can see the Gif as 1 picture it doesn't animates
only static picture :( what should I add to make it animated ?
#import <Cocoa/Cocoa.h>
#import <Quartz/Quartz.h>//for drawing circle
#import "sharedPrefferences.h"
#interface GenericFanSubView : NSView
{
NSColor * _backgroundColor;
NSImageView* imageView;
}
- (void)setBackgroundColor :(NSColor*)color;
- (void)insertGif1;
- (void)insertGif2;
- (void)insertGif3;
#end
#import "GenericFanSubView.h"
#define PI 3.14285714285714
#implementation GenericFanSubView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
imageView = [[NSImageView alloc]initWithFrame:CGRectMake(0, 0,self.frame.size.width,self.frame.size.height)];
[imageView setAnimates: YES];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
// Drawing code here.
[self drawCircleInRect];
_backgroundColor = [NSColor whiteColor];
[self insertGif1];
}
-(void)drawCircleInRect
{
//draw colored circle here
CGContextRef context = [[NSGraphicsContext // 1
currentContext] graphicsPort];
// ********** Your drawing code here ********** // 2
CGContextSetFillColorWithColor(context,[self NSColorToCGColor:(_backgroundColor)]);
float radius1 = self.frame.size.height/2;
float startAngle = 0;
float endAngle = endAngle = PI*2;
CGPoint position = CGPointMake(self.frame.size.height/2,self.frame.size.height/2);//center of the view
CGContextBeginPath(context);
CGContextAddArc(context, position.x, position.y, radius1, startAngle, endAngle, 1);
CGContextDrawPath(context, kCGPathFill); // Or kCGPathFill
}
- (void)setBackgroundColor :(NSColor*)color
{
_backgroundColor = color;
[self setNeedsDisplay:YES];
}
- (CGColorRef)NSColorToCGColor:(NSColor *)color
{
NSInteger numberOfComponents = [color numberOfComponents];
CGFloat components[numberOfComponents];
CGColorSpaceRef colorSpace = [[color colorSpace] CGColorSpace];
[color getComponents:(CGFloat *)&components];
CGColorRef cgColor = CGColorCreate(colorSpace, components);
return cgColor;
}
//curentlly calling only this 1
- (void)insertGif1
{
[imageView removeFromSuperview];
[imageView setImageScaling:NSImageScaleNone];
[imageView setAnimates: YES];
imageView.image = [NSImage imageNamed:#"FanBlades11.gif"];
[self addSubview:imageView];
}
#end
Edit: I discovered the source of the problem:
I was adding my class (that represents gif inside the circle) on top of RMBlurredView
and the animations doesn't work when I adding it as subview ,However it works on all the other views I added.
Any ideas what could be the reason inside the RMBlurredView to stop my NSImageView from animating ?
Edit:
I think [self setWantsLayer:YES]; is the reason I am not getting animations
how can I still get the animation with this feature enabled?
Edit:
Here is a simple sample with my problem
http://snk.to/f-cdk3wmfn
my gif:This is my gif it is invisible on white background color
"You must disable the autoscaling feature of the NSImageView for the
animation playback to function. After you've done that, no extra
programming required. It works like a charm!"
--http://www.cocoabuilder.com/archive/cocoa/108530-nsimageview-and-animated-gifs.html
imageView.imageScaling = NSImageScaleNone;
imageView.animates = YES;
needed for layer backed views:
if the image view is in a layer backed view or is layer backed itself:
imageView.canDrawSubviewsIntoLayer = YES;
working example using the question's own gif:
NSImageView *view = [[NSImageView alloc] initWithFrame:CGRectMake(10, 10, 50, 50)];
view.imageScaling = NSImageScaleNone;
view.animates = YES;
view.image = [NSImage imageNamed:#"FanBlades2_42x42.gif"];
view.canDrawSubviewsIntoLayer = YES;
NSView *layerview = [[NSView alloc] initWithFrame:CGRectMake(0, 0, 60, 60)];
layerview.wantsLayer = YES;
[layerview addSubview:view];
[self.window.contentView addSubview:layerview];

UIImage/UIImageView redraw when containing UIView is scaled

My iPad app has a navigation where I show screenshots of the different pages and because I want to show more than one screenshot at once I scale the container to around 24% of the original screenshots (1024x768).
- (void) loadView
{
// get landscape screen frame
CGRect screenFrame = [UIScreen mainScreen].bounds;
CGRect landscapeFrame = CGRectMake(0, 0, screenFrame.size.height, screenFrame.size.width);
UIView *view = [[UIView alloc] initWithFrame:landscapeFrame];
view.backgroundColor = [UIColor grayColor];
self.view = view;
// add container view for 2 images
CGRect startFrame = CGRectMake(-landscapeFrame.size.width/2, 0, landscapeFrame.size.width*2, landscapeFrame.size.height);
container = [[UIView alloc] initWithFrame:startFrame];
container.backgroundColor = [UIColor whiteColor];
// add image 1 (1024x768)
UIImage *img1 = [UIImage imageNamed:#"01.jpeg"];
UIImageView *img1View = [[UIImageView alloc] initWithImage:img1];
[container addSubview:img1View];
// add image 2 (1024x768)
UIImage *img2 = [UIImage imageNamed:#"02.jpeg"];
UIImageView *img2View = [[UIImageView alloc] initWithImage:img2];
// move img2 to the right of img1
CGRect newFrame = img2View.frame;
newFrame.origin.x = 1024.0;
img2View.frame = newFrame;
[container addSubview:img2View];
// scale to 24%
container.transform = CGAffineTransformMakeScale(0.24, 0.24);
[self.view addSubview:container];
}
but when I scale images with "small" text it looks sth like this:
I have to use the big screenshots because if a user taps the image it should scale to 100% and be crispy clear.
is there a way how I can scale the images "smoothly" (on the fly) without ruining performance?
it would be enough to have two versions: the full-px one and another for the 24% version.
The reason the scaled-down image looks crappy is it's being scaled in OpenGL, which is using fast-but-low-quality linear interpolation. As you probably know, UIView is built on top of CALayer, which is in turn a sort of wrapper for OpenGL textures. Because the contents of the layer reside in the video card, CALayer can do all of its magic on the GPU, independent of whether the CPU is busy loading a web site, blocked on disk access, or whatever. I mention this only because it's useful to pay attention to what's actually in the textures inside your layers. In your case, the UIImageView's layer has the full 1024x768 bitmap image on its texture, and that isn't affected by the container's transform: The CALayer inside the UIImageView doesn't see that it's going to be (let's see..) 246x185 on-screen and re-scale its bitmap, it just lets OpenGL do its thing and scale down the bitmap every time it updates the display.
To get better scaling, we'll need to do it in CoreGraphics instead of OpenGL. Here's one way to do it:
- (UIImage*)scaleImage:(UIImage*)image by:(float)scale
{
CGSize size = CGSizeMake(image.size.width * scale, image.size.height * scale);
UIGraphicsBeginImageContextWithOptions(size, YES, 0.0);
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return imageCopy;
}
- (void)loadView
{
// get landscape screen frame
CGRect screenFrame = [UIScreen mainScreen].bounds;
CGRect landscapeFrame = CGRectMake(0, 0, screenFrame.size.height, screenFrame.size.width);
UIView *view = [[UIView alloc] initWithFrame:landscapeFrame];
view.backgroundColor = [UIColor grayColor];
self.view = view;
// add container view for 2 images
CGRect startFrame = CGRectMake(-landscapeFrame.size.width/2, 0, landscapeFrame.size.width*2, landscapeFrame.size.height);
container = [[UIView alloc] initWithFrame:startFrame];
container.backgroundColor = [UIColor whiteColor];
// add image 1 (1024x768)
UIImage *img1 = [UIImage imageNamed:#"01.png"];
img1View = [[TapImageView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)];
img1View.userInteractionEnabled = YES; // important!
img1View.image = [self scaleImage:img1 by:0.24];
[container addSubview:img1View];
// add image 2 (1024x768)
UIImage *img2 = [UIImage imageNamed:#"02.png"];
img2View = [[TapImageView alloc] initWithFrame:CGRectMake(1024, 0, 1024, 768)];
img2View.userInteractionEnabled = YES;
img2View.image = [self scaleImage:img2 by:0.24];
[container addSubview:img2View];
// scale to 24% and layout subviews
zoomed = YES;
container.transform = CGAffineTransformMakeScale(0.24, 0.24);
[self.view addSubview:container];
}
- (void)viewTapped:(id)sender
{
zoomed = !zoomed;
[UIView animateWithDuration:0.5 animations:^
{
if ( zoomed )
{
container.transform = CGAffineTransformMakeScale(0.24, 0.24);
}
else
{
img1View.image = [UIImage imageNamed:#"01.png"];
img2View.image = [UIImage imageNamed:#"02.png"];
container.transform = CGAffineTransformMakeScale(1.0, 1.0);
}
}
completion:^(BOOL finished)
{
if ( zoomed )
{
UIImage *img1 = [UIImage imageNamed:#"01.png"];
img1View.image = [self scaleImage:img1 by:0.24];
UIImage *img2 = [UIImage imageNamed:#"02.png"];
img2View.image = [self scaleImage:img2 by:0.24];
}
}];
}
And here's TapImageView, a UIImageView subclass that tells us when it's been tapped by sending an action up the responder chain:
#interface TapImageView : UIImageView
#end
#implementation TapImageView
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
[[UIApplication sharedApplication] sendAction:#selector(viewTapped:) to:nil from:self forEvent:event];
}
#end
Instead of scaling the container and all of its subviews. Create a UIImageView from the contents of the container and adjust its frame size to 24% of the original.
UIGraphicsBeginImageContext(container.bounds.size);
[container renderInContext:UIGraphicsGetCurrentContext()];
UIImage *containerImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *containerImageView = [[UIImageView alloc] initWithImage:containerImage];
CGRectFrame containerFrame = startFrame;
containerFrame.size.with *= 0.24;
containerFrame.size.height *= 0.24;
containerImageView.frame = containerFrame;
[self.view addSubView:containerImageView];

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)
}
}

Rotating a full screen UIImageView

I have two images:
Help-Portrait.png (320 x 480)
Help-Landscape.png (480 x 320)
When a user clicks the help button on any view, they need to be presented with the correct image, which should also rotate when the device does. I have tried adding the imageView to both the window, and the navigation controller view.
For some reason I am having issues with this.
Could anyone shed light on what I am doing wrong?
UIImage *image = nil;
CGRect frame;
if (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation])) {
image = [UIImage imageNamed:#"Help-Portrait.png"];
frame = CGRectMake(0, 0, 320, 480);
} else {
image = [UIImage imageNamed:#"Help-Landscape.png"];
frame = CGRectMake(0, 0, 480, 320);
}
if (!helpImageView) {
helpImageView = [[UIImageView alloc] initWithFrame:frame];
helpImageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
helpImageView.image = image;
}
[[UIApplication sharedApplication] setStatusBarHidden:YES];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(helpImageTapped:)];
helpImageView.userInteractionEnabled = YES;
[helpImageView addGestureRecognizer:tap];
[self.view addSubview:helpImageView];
[tap release];
willRotateToInterfaceOrientation:
if(helpImageView) {
[(id)[UIApplication sharedApplication] setStatusBarHidden:NO animated:YES];
if (UIInterfaceOrientationIsPortrait(toInterfaceOrientation)) {
helpImageView.image = [UIImage imageNamed:#"Help-Portrait.png"];
} else {
helpImageView.image = [UIImage imageNamed:#"Help-Landscape.png"];
}
}
When you rotate the device the image and the frame don't change, and you end up with two thirds of the portrait image displayed on the left part of the screen.
What I want is it for it to show the correct image for the orientation, the right way up. Also I would like animation for the image rotation, but thats a side issue
The place where you need to adjust your button image is in your ViewController's shouldAutorotateToInterfaceOrientation method (documentation linked for you).
Do something like:
- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation
{
UIImage *image = NULL;
if (UIInterfaceOrientationIsPortrait(interfaceOrientation))
{
image = [UIImage imageNamed:#"Help-Portrait.png"];
} else {
image = [UIImage imageNamed:#"Help-Landscape.png"];
}
[yourButton setImage: image forState: UIControlStateNormal]
return YES;
}
Michael Dautermann's answer looks to have almost all the answer, but I'm opposed to using shouldAutorotateToInterfaceOrientation. This method is designed only to determine if a rotation should or should not occur, nothing else.
You should use either didRotateFromInterfaceOrientation: willAnimateRotationToInterfaceOrientation:duration instead.
didRotateFromInterfaceOrientation: - interfaceOrientation is already set on your UIViewController so you can get the current orientation. In this case the rotation animation is already complete.
willAnimateRotationToInterfaceOrientation:duration - The benefit of this method is execution time. You are inside the rotation animation so you won't have the less than pretty effects which happens when you change UI either after the rotation animation completes.
Got it working, with this code:
- (void)showHelpImage {
NSString *imageName = #"Help_Portrait.png";
CGRect imageFrame = CGRectMake(0, 0, 320, 480);
helpImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:imageName]];
helpImageView.frame = imageFrame;
[self.view addSubview:helpImageView];
[self updateHelpImageForOrientation:self.interfaceOrientation];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(helpImageTapped:)];
helpImageView.userInteractionEnabled = YES;
[helpImageView addGestureRecognizer:tap];
[self.view addSubview:helpImageView];
[tap release];
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self updateHelpImageForOrientation:toInterfaceOrientation];
}
- (void)updateHelpImageForOrientation:(UIInterfaceOrientation)orientation {
NSString *imageName = nil;
CGRect imageFrame = helpImageView.frame;
if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown) {
imageName = #"Help_Portrait.png";
imageFrame = CGRectMake( 0, 0, 320, 480);
} else if (orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight) {
imageName = #"Help_Landscape.png";
imageFrame = CGRectMake( 0, 0, 480, 320);
}
helpImageView.image = [UIImage imageNamed:imageName];
helpImageView.frame = imageFrame;
}
Got the idea from:
http://www.dobervich.com/2010/10/22/fade-out-default-ipad-app-image-with-proper-orientation/

Using a UIScrollview to zoom/pan an image, place items on top of image and move them

I am using a uiscrollview to display an UIImageview that is larger then screen size (just like photo viewing in the Photo Library). next i needed to add a point (i am using a UIImageview to show a dot) to the screen when the user taps. got that working.
now what i am trying to do is have that point to stay in the same location relative to the image. UIScrollview has some notifications for when dragging starts and ends, but nothing that gives a constant update of its contentoffset. say i place a dot at X location. i then scroll down 50 pixels, the point needs to move up 50 pixels smoothly (so the point always stays at the same location relative to the image).
does anybody have any ideas?
EDIT: adding code
- (void)viewDidLoad
{
[super viewDidLoad];
//other code...
scrollView.delegate = self;
scrollView.clipsToBounds = YES;
scrollView.decelerationRate = UIScrollViewDecelerationRateFast;
scrollView.minimumZoomScale = (scrollView.frame.size.width / imageView.frame.size.width);
scrollView.maximumZoomScale = 10.0;
//other code...
}
adding a point
-(void) foundTap:(UITapGestureRecognizer *) recognizer
{
CGPoint pixelPos = [recognizer locationInView:self.view];
UIImageView *testPoint = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"inner-circle.png"]];
testPoint.frame = CGRectMake(0, 0, 20, 20);
testPoint.center = CGPointMake(pixelPos.x, pixelPos.y);
[self.scrollView addSubview:testPoint];
NSMutableArray *tempTestPointArray = [[NSMutableArray alloc] initWithArray:testPointArray];
[tempTestPointArray addObject:testPoint];
testPointArray = tempTestPointArray;
NSLog(#"recorded point %f,%f",pixelPos.x,pixelPos.y);
}
getting pixel points
-(void)scrollViewDidZoom:(UIScrollView *)scrollView
{
NSLog(#"zoom factor: %f", scrollView.zoomScale);
UIPinchGestureRecognizer *test = scrollView.pinchGestureRecognizer;
UIView *piece = test.view;
CGPoint locationInView = [test locationInView:piece];
CGPoint locationInSuperview = [test locationInView:piece.superview];
NSLog(#"locationInView: %# locationInSuperView: %#", NSStringFromCGPoint(locationInView), NSStringFromCGPoint(locationInSuperview));
}
You can implement the UIScrollViewDelegate method scrollViewDidScroll: to get a continuous callback whenever the content offset changes.
Can you not simply add the image view with the dot as a subview of the scroll view?
You can use something like this:
UIGraphicsBeginImageContext(mainimg.frame.size);
[mainimg.image drawInRect:
CGRectMake(0, 0, mainimg.frame.size.width, mainimg.frame.size.height)];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineCap(ctx, kCGLineCapButt);
CGContextSetLineWidth(ctx,2.0);
CGContextSetRGBStrokeColor(ctx, 1.0, 0.0, 0.0, 1.0);
CGContextBeginPath(ctx);
if (largecursor == FALSE) {
CGContextMoveToPoint(ctx, pt.x-3.0, pt.y);
CGContextAddLineToPoint(ctx, pt.x+3.0,pt.y);
CGContextMoveToPoint(ctx, pt.x, pt.y-3.0);
CGContextAddLineToPoint(ctx, pt.x, pt.y+3.0);
} else {
CGContextMoveToPoint(ctx, 0.0, pt.y);
CGContextAddLineToPoint(ctx, maxx,pt.y);
CGContextMoveToPoint(ctx, pt.x, 0.0);
CGContextAddLineToPoint(ctx, pt.x, maxy);
}
CGContextStrokePath(ctx);
mainimg.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();