CATiledLayer subview causing memory warning and crash [closed] - ios

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
Alright, here's what's happening... I have a UIView subclass, which includes two points (start and end), a CGPath between those two points and lot of UILabels. If I add that UIView subclass as a subview to my scrollview (over top of my CATiledLayer based UIView) then my app starts getting memory warnings and eventually crashes. However, if I remove the UIView subclass that contains the points and path and leave the CATiledLayer view as it is, everything functions perfectly and no crashing or memory warnings occur.
Does anyone have any ideas on why this is occurring? The content view shouldn't have any problems being so big should it since I'm only draw labels that are at max 40px wide and a CGPath, which is also pretty small?
This is a mapping application
Here's some code:
//Display the map image
- (void)displayTiledImageNamed:(NSString *)imageName size:(CGSize)imageSize {
self.zoomScale = 1.0;
container = [[UIView alloc] initWithFrame:(CGRect){ CGPointZero, imageSize }];
_zoomView = [[UIImageView alloc] initWithFrame:(CGRect){ CGPointZero, imageSize }];
NSString *path = [NSString stringWithFormat:#"%#/%#-Small.png",[[NSBundle mainBundle] bundlePath], [imageName stringByDeletingPathExtension]];
UIImage *zoomImage = [UIImage imageWithContentsOfFile:path];
[_zoomView setImage:zoomImage];
[container addSubview:_zoomView];
[_zoomView release];
_tilingView = [[UMTileView alloc] initWithImageName:imageName size:imageSize];
_tilingView.frame = _zoomView.bounds;
[_zoomView addSubview:_tilingView];
[_tilingView release];
[self configureForImageSize:imageSize];
//This is the view that's causing the crash and memory warnings
//If this view is commented out the app functions just fine
UMMapContentView *m = [[UMMapContentView alloc] initWithFrame:CGRectMake(0,0,imageSize.width,imageSize.height) andColors:self.colors mapView:self.mapView];
self.contentView = m;
[container addSubview:self.contentView];
[m release];
[self addSubview:container];
[container release];
}
This my path view class which is another subview inside the UMMapContentView view above:
+ (Class)layerClass {
return [CATiledLayer class];
}
+ (CFTimeInterval)fadeDuration {
return 0.0;
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.userInteractionEnabled = NO;
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (void)dealloc {
self.path = nil;
self.pathColor = nil;
[super dealloc];
}
- (void)resetPathView {
CATiledLayer *tiledLayer = (CATiledLayer*)self.layer;
tiledLayer.contents = nil;
[tiledLayer setNeedsDisplay];
}
- (void)drawPath:(UMPath*)p {
self.path = p;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
if (self.path) {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, self.pathColor.CGColor);
CGContextSetShadowWithColor(context, CGSizeMake(2.0, 1.0), 1.0, [[UIColor colorWithWhite:0.2 alpha:0.8] CGColor]);
CGContextSetLineWidth(context, 8.0);
CGContextBeginPath (context);
CGContextMoveToPoint (context, ((UMMapPoint*)[self.path.points objectAtIndex: 0]).x, ((UMMapPoint*)[self.path.points objectAtIndex: 0]).y);
for (int i = 1; i < self.path.points.count; i++) {
CGContextAddQuadCurveToPoint (context, ((UMMapPoint*)[self.path.points objectAtIndex:i-1]).x, ((UMMapPoint*)[self.path.points objectAtIndex: i-1]).y, ((UMMapPoint*)[self.path.points objectAtIndex:i]).x, ((UMMapPoint*)[self.path.points objectAtIndex:i]).y);
}
CGContextStrokePath (context);
}
}
And this is my method that places all the UILabels in the UMMapContentView view:
- (void)mapAllLabelsInNavigationMap:(int)navigationMap {
//There are 1109 label dictionaries in the labels array
for (NSDictionary *dict in labels) {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake([[dict objectForKey:#"X"] floatValue],[[dict objectForKey:#"Y"] floatValue],[[dict objectForKey:#"Width"] floatValue],[[dict objectForKey:#"Height"] floatValue])];
[label setLineBreakMode:NSLineBreakByWordWrapping];
[label setNumberOfLines:0];
[label setBackgroundColor:[UIColor clearColor]];
[label setTextColor:[self colorFromHexString:[dict objectForKey:#"Foreground"]]];
[label setFont:[UIFont systemFontOfSize:[[dict objectForKey:#"FontSize"] floatValue]]];
[label setTextAlignment:NSTextAlignmentCenter];
[label setText:[[UMDataBase shared] stringForID:[[dict objectForKey:#"Texts_ID"] intValue] withLanguage:languageID]];
[self addSubview:label];
[localMapLabels addObject:label];
[label release];
}
}
I'm not really sure what direction I need to head... Any help would be much appreciated! :)

You did not give the source code of UMMapContentView, but if it uses CATiledLayer, then that may be the problem. It seems that you cannot subview a CATiledLayer-backed view.
Scroll to the bottom (in the comments section): http://red-glasses.com/index.php/tutorials/catiledlayer-how-to-use-it-how-it-works-what-it-does/
It may be easier if you write your own tiled layer anyhow. Documentation on CATiledLayer is sparse while the bugs are aplenty.

Related

iOS UIImageView memory not getting deallocated on ARC

I want to animate an image view in circular path and on click of image that image view need to change the new image. My problem is the images i allocated to the image view is not deallocated. And app receives memory warning and crashed. I surfed and tried lot of solutions for this problem but no use. In my case i need to create all ui components from Objective c class. Here am posting the code for creating image view and animation.
#autoreleasepool {
for(int i= 0 ; i < categories.count; i++)
{
NSString *categoryImage = [NSString stringWithFormat:#"%#_%ld.png",[categories objectAtIndex:i],(long)rating];
if (paginationClicked) {
if([selectedCategories containsObject:[categories objectAtIndex:i]]){
categoryImage = [NSString stringWithFormat:#"sel_%#",categoryImage];
}
}
UIImageView *imageView = [[UIImageView alloc] init];
imageView.image = [self.mySprites objectForKey:categoryImage];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.clipsToBounds = NO;
[imageView sizeToFit];
imageView.accessibilityHint = [categories objectAtIndex:i];
// imageView.frame = CGRectMake(location.x+sin(M_PI/2.5)*(self.view.frame.size.width*1.5),location.y+cos(M_PI/2.5)*(self.view.frame.size.width*1.5) , 150, 150);
imageView.userInteractionEnabled = YES;
imageView.multipleTouchEnabled = YES;
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(categoryTapGestureCaptured:)];
singleTap.numberOfTapsRequired = 1;
[imageView addGestureRecognizer:singleTap];
[categoryView addSubview:imageView];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:location
radius:self.view.frame.size.width*1.5
startAngle:0.8
endAngle:-0.3+(0.1*(i+1))
clockwise:NO];
animation.path = path.CGPath;
imageView.center = path.currentPoint;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.duration = 1+0.25*i;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
// Apply it
[imageView.layer addAnimation:animation forKey:#"animation.trash"];
}
}
And this is the code to change the image on click.
for (UIImageView *subview in subviews) {
NSString *key = [NSString stringWithFormat:#"%#_%ld.png",subview.accessibilityHint,(long)rating];
if ([SelectedCategory isEqualToString:subview.accessibilityHint]) {
NSString *tempSubCategory = [categoryObj objectForKey:SelectedCategory];
if([selectedCategories containsObject:SelectedCategory]){
subview.image = [self.mySprites objectForKey:key];
[selectedCategories removeObject:SelectedCategory];
if (tempSubCategory.length != 0) {
subCategoriesAvailable = subCategoriesAvailable-1;
}
[self showNoPagination:subCategoriesAvailable+2];
}else{
if(selectedCategories.count != 2){
key = [NSString stringWithFormat:#"sel_%#",key];
subview.image = [self.mySprites objectForKey:key];
[selectedCategories addObject:SelectedCategory];
if ([SelectedCategory isEqualToString:#"Other"]) {
[self showCommentDialog];
}else{
if (tempSubCategory.length != 0) {
subCategoriesAvailable = subCategoriesAvailable+1;
}
[self showNoPagination:subCategoriesAvailable+2];
}
}
}
[self disableCategories];
break;
}
}
And i don't know what am doing wrong here. I tried nullifying on for loop but no use.
Code which i used for removing the image view
UIView *categoryView = [self.view viewWithTag:500];
NSArray *subviews = [categoryView subviews];
for (UIImageView *subview in subviews) {
if(![selectedCategories containsObject:subview.accessibilityHint]){
[subview removeFromSuperview];
subview.image = Nil;
}
}
Adding sprite reader code for reference
#import "UIImage+Sprite.h"
#import "XMLReader.h"
#implementation UIImage (Sprite)
+ (NSDictionary*)spritesWithContentsOfFile:(NSString*)filename
{
CGFloat scale = [UIScreen mainScreen].scale;
NSString* file = [filename stringByDeletingPathExtension];
if ([[UIScreen mainScreen] respondsToSelector:#selector(displayLinkWithTarget:selector:)] &&
(scale == 2.0))
{
file = [NSString stringWithFormat:#"%##2x", file];
}
NSString* extension = [filename pathExtension];
NSData* data = [NSData dataWithContentsOfFile:[NSString stringWithFormat:#"%#.%#", file,extension]];
NSError* error = nil;
NSDictionary* xmlDictionary = [XMLReader dictionaryForXMLData:data error:&error];
NSDictionary* xmlTextureAtlas = [xmlDictionary objectForKey:#"TextureAtlas"];
UIImage* image = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:#"%#.%#", file,[[xmlTextureAtlas objectForKey:#"imagePath"]pathExtension]]];
CGSize size = CGSizeMake([[xmlTextureAtlas objectForKey:#"width"] integerValue],
[[xmlTextureAtlas objectForKey:#"height"] integerValue]);
if (!image || CGSizeEqualToSize(size, CGSizeZero)) return nil;
CGImageRef spriteSheet = [image CGImage];
NSMutableDictionary* tempDictionary = [[NSMutableDictionary alloc] init];
NSArray* xmlSprites = [xmlTextureAtlas objectForKey:#"sprite"];
for (NSDictionary* xmlSprite in xmlSprites)
{
CGRect unscaledRect = CGRectMake([[xmlSprite objectForKey:#"x"] integerValue],
[[xmlSprite objectForKey:#"y"] integerValue],
[[xmlSprite objectForKey:#"w"] integerValue],
[[xmlSprite objectForKey:#"h"] integerValue]);
CGImageRef sprite = CGImageCreateWithImageInRect(spriteSheet, unscaledRect);
// If this is a #2x image it is twice as big as it should be.
// Take care to consider the scale factor here.
[tempDictionary setObject:[UIImage imageWithCGImage:sprite scale:scale orientation:UIImageOrientationUp] forKey:[xmlSprite objectForKey:#"n"]];
CGImageRelease(sprite);
}
return [NSDictionary dictionaryWithDictionary:tempDictionary];
}
#end
Please help me to resolve this. Thanks in advance.
It looks like all the images are being retained by the dictionary(assumption) self.mySprites, as you are loading them with the call imageView.image = [self.mySprites objectForKey:categoryImage];
If you loaded the images into the dictionary with +[UIImage imageNamed:], then the dictionary initially contains only the compressed png images. Images are decompressed from png to bitmap as they are rendered to the screen, and these decompressed images use a large amount of RAM (that's the memory usage you're seeing labeled "ImageIO_PNG_Data"). If the dictionary is retaining them, then the memory will grow every time you render a new one to the screen, as the decompressed data is held inside the UIImage object retained by the dictionary.
Options available to you:
Store the image names in the self.mySprites dictionary, and load the images on demand. You should be aware that +[UIImage imageNamed:] implements internal RAM caching to speed things up, so this might also cause memory issues for you if the images are big, as the cache doesn't clear quickly. If this is an issue, consider using +[UIImage imageWithContentsOfFile:], although it requires some additional code (not much), which doesn't cache images in RAM.
Re-implement self.mySprites as an NSCache. NSCache will start throwing things out when the memory pressure gets too high, so you'll need to handle the case that the image is not there when you expect it to be, and load it from disk (perhaps using the above techniques)
CAKeyframeAnimation inherits from CAPropertyAnimation which in tern is inherited from CAAnimation.
If you see the delegate of CAAnimation class, it is strongly referenced as written below as it is declared -
/* The delegate of the animation. This object is retained for the
* lifetime of the animation object. Defaults to nil. See below for the
* supported delegate methods. */
#property(strong) id delegate;
Now you have added the reference of animation on imageView.layer, doing so the reference of imageView.layer will be strongly retained by CAAnimation reference.
Also you have set
animation.removedOnCompletion = NO;
which won't remove the animation from layer on completion
So if you are done with a image view then first removeAllAnimations from its layer and then release the image view.
I think as the CAAnimation strongly refers the imageView reference(it would also have increased it's retain count) and this could be the reason that you have removed the imageView from superview, after which it's retain count would still not be zero, and so leading to a leak.
Is there any specific requirement to set
animation.removedOnCompletion = NO;
since, setting
animation.removedOnCompletion = YES;
could solve the issue related to memory leak.
Alternatively, to resolve memory leak issue you can remove the corresponding animation on corresponding imageView' layer, by implementing delegate of CAAnimation, like as shown below -
/* Called when the animation either completes its active duration or
* is removed from the object it is attached to (i.e. the layer). 'flag'
* is true if the animation reached the end of its active duration
* without being removed. */
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    if (flag) {
        NSLog(#"%#", #"The animation is finished. Do something here.");
    }
//Get the reference of the corresponding imageView
    [imageView.layer removeAnimationForKey:#"animation.trash"];
}
UIView *categoryView = [self.view viewWithTag:500];
NSArray *subviews = [categoryView subviews];
for (UIImageView *subview in subviews) {
if(![selectedCategories containsObject:subview.accessibilityHint]){
[subview.layer removeAnimationForKey:#"animation.trash"]; // either this
//or// [subview.layer removeAllAnimations]; //alternatively
[subview removeFromSuperview];
subview.image = Nil;
}
}

Drawing 2 UIImage merged with different UILabel

I've been trying to create a for loop which take UILabel.text and merge it into an UIImage then save it into an instance and then when getting into the for loop second time it would save the result in different instance.
The problem that I'm facing is that it is saving the first UIImage but with low quality and skipping the second one. I've even tried to do the UIImage saving code in a button action but still didn't work.
-(void)CreateTheImage{
UIImage * img = [UIImage imageNamed:#"SquareBackground.jpg"];
UIGraphicsBeginImageContext(_imgView.bounds.size);
int Counter = 0;
for (UIView * view in [_imgView subviews]){
[view removeFromSuperview];
}
NSString *txt1 = #"Test";
NSString *txt2 = #"Worked Well";
[array addObject:txt1];
[array addObject:txt2];
UILabel *lbl = [[UILabel alloc]init];
NSLog(#"%i",[array count]);
NSLog(#"Before the loop");
for (int x = 0; x < [array count]; x++) {
NSLog(#"Entered the loop");
NSLog(#"x = %i",x);
lbl.text = [array objectAtIndex:x];
NSLog(#"lbl.text = %#",[array objectAtIndex:x]);
lbl.textAlignment = NSTextAlignmentCenter;
[lbl setBackgroundColor:[UIColor clearColor]];
lbl.font = [UIFont boldSystemFontOfSize:16];
[lbl setFrame:CGRectMake(20,112.5, 260, 75)];
[_imgView addSubview:lbl];
[_imgView bringSubviewToFront:lbl];
[_imgView setImage:img];
[_imgView.layer renderInContext:UIGraphicsGetCurrentContext()];
Counter = x+1;
NSLog(#"counter = %i",Counter);
switch (Counter) {
case 1:
NSLog(#"swintch case 1");
newImage1 = UIGraphicsGetImageFromCurrentImageContext();
break;
case 2:
NSLog(#"swintch case 2");
newImage2 = UIGraphicsGetImageFromCurrentImageContext();
break;
default:
break;
}
NSLog(#"after switch");
UIGraphicsEndImageContext();
}
//To save the picture in the album
UIImageWriteToSavedPhotosAlbum(newImage1, nil, nil, nil);
UIImageWriteToSavedPhotosAlbum(newImage2, nil, nil, nil);
NSLog(#"After the loop");
}
Also when I run this method on my iPhone it gives me a warning which is
CreatingVideo[334] : CGContextSaveGState: invalid context 0x0. This is a serious error. This application, or a library it uses, is using an invalid context and is thereby contributing to an overall degradation of system stability and reliability. This notice is a courtesy: please fix this problem. It will become a fatal error in an upcoming update.
Your code doesn't make sense. You have a for loop that runs through an array, adds images to an image view, captures 1 of 2 images from the current off-screnen context, and then ends the image context after the first pass through the array. On the second pass, the context doesn't exist any more, so the second end context call will fail.
Move the end context outside of your for loop. That should fix your warning.
Even then, I'm not sure if it will work to remove subviews from an image view, add new views, an render the content to a graphics context, all without returning. UIKit view drawing usually queues changes to the UI until your code returns, then renders it on the next pass through the event loop.
Thanks to Duncan C for helping me find the answer and correct the code to work perfectly now
-(void)CreateTheImage{
UIImage * img = [UIImage imageNamed:#"SquareBackground.jpg"];
UIGraphicsBeginImageContextWithOptions(CGSizeMake(320, 320), NO, 0.0f);
int Counter = 0;
NSString *txt1 = #"Test";
NSString *txt2 = #"Worked Well";
[array addObject:txt1];
[array addObject:txt2];
UILabel *lbl = [[UILabel alloc]init];
NSLog(#"%i",[array count]);
NSLog(#"Before the loop");
for (int x = 0; x < [array count]; x++) {
for (UIView * view in [_imgView subviews]){
[view removeFromSuperview];
}
NSLog(#"Entered the loop");
NSLog(#"x = %i",x);
lbl.text = [array objectAtIndex:x];
NSLog(#"lbl.text = %#",[array objectAtIndex:x]);
lbl.textAlignment = NSTextAlignmentCenter;
[lbl setBackgroundColor:[UIColor clearColor]];
lbl.font = [UIFont boldSystemFontOfSize:25];
[lbl setFrame:CGRectMake(20,122.5, 280, 75)];
[_imgView addSubview:lbl];
[_imgView bringSubviewToFront:lbl];
[_imgView setImage:img];
[_imgView.layer renderInContext:UIGraphicsGetCurrentContext()];
Counter = x+1;
NSLog(#"counter = %i",Counter);
switch (Counter) {
case 1:
newImage1 = UIGraphicsGetImageFromCurrentImageContext();
NSLog(#"swintch case 1");
break;
case 2:
NSLog(#"swintch case 2");
newImage2 = UIGraphicsGetImageFromCurrentImageContext();
break;
default:
break;
}
NSLog(#"after switch");
}
UIGraphicsEndImageContext();
//To save the picture in the album
UIImageWriteToSavedPhotosAlbum(newImage1, nil, nil, nil);
UIImageWriteToSavedPhotosAlbum(newImage2, nil, nil, nil);
NSLog(#"After the loop");
}

CATiledLayer, iOS7 tiles not updating

In upgrading from iOS6 to iOS7, I think I've found a bug with CATiledLayer, and I'd like to have it verified by the community before submitting to Apple.
The issue is that if you have a UIScrollView with many CATiledLayers within it, the tiles will eventually stop updating.
I have a sample project demonstrating the issue here:
https://github.com/sbudhram/CATiledLayerBug
Please download and run on an iPad running iOS6 vs iOS7.
This project generates 900 CATiledLayers within a UIScrollView, at 3 levels of resolution. As the user zooms in, the tiles update to more refined resolution. The code works on iOS6, but tiles eventually stop updating on iOS7.
I've googled around to see if anyone's had similar problems to this, and found this:
http://www.cocoanetics.com/2013/09/welcome-to-ios-7-issues/
This is different though, because I believe this can happen without a memory warning.
Here is the relevant piece of code in UIViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
_scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_scrollView.backgroundColor = [UIColor yellowColor];
_scrollView.contentSize = CGSizeMake(self.view.frame.size.width, self.view.frame.size.height);
_scrollView.minimumZoomScale = 1;
_scrollView.maximumZoomScale = 4.1;
_scrollView.zoomScale = 2;
_scrollView.showsHorizontalScrollIndicator = YES;
_scrollView.showsVerticalScrollIndicator = YES;
_scrollView.delegate = self;
[self.view addSubview:_scrollView];
self.contentView = [[UIView alloc] initWithFrame:_scrollView.bounds];
_contentView.backgroundColor = [UIColor lightGrayColor];
[_scrollView addSubview:_contentView];
CGFloat tileSize = 20.0f;
CGFloat tileSpacing = 4.0f;
for (int i = 0; i < 30; i++) {
for (int j = 0; j < 30; j++) {
CATiledLayer *tLayer = [CATiledLayer layer];
tLayer.bounds = CGRectMake(0, 0, tileSize, tileSize);
tLayer.position = CGPointMake(tileSize/2 + i*(tileSpacing+tileSize), tileSize/2 + j*(tileSpacing+tileSize));
tLayer.delegate = self;
tLayer.contentsGravity = kCAGravityResize;
tLayer.contentsScale = [[UIScreen mainScreen] scale];
tLayer.masksToBounds = NO;
tLayer.opacity = 1.0f;
tLayer.backgroundColor = [UIColor colorWithRed:.2 green:.2 blue:.8 alpha:.5].CGColor;
tLayer.levelsOfDetail = 3;
tLayer.levelsOfDetailBias = 3;
tLayer.tileSize = CGSizeMake(1024., 1024.);
[_contentView.layer addSublayer:tLayer];
}
}
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
NSLog(#"Zoom: %f", scrollView.zoomScale);
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return _contentView;
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
UIImage *drawImage = nil;
if (_scrollView.zoomScale < 2) {
drawImage = [UIImage imageNamed:#"low.png"];
NSLog(#"Drawing - Low");
}
else if (_scrollView.zoomScale < 4) {
drawImage = [UIImage imageNamed:#"med.png"];
NSLog(#"Drawing - Med");
}
else {
drawImage = [UIImage imageNamed:#"high.png"];
NSLog(#"Drawing - Hi");
}
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -layer.bounds.size.height);
CGContextDrawImage(ctx, layer.bounds, [drawImage CGImage]);
}
Here's a snapshot of what happens on iOS7 (everything is filled in on iOS6):
As mentioned by gavi, this now works in 7.1.

Draw text outside of CGRect.

I am using Apple's Sample Code TheElements for this question. How would I go about drawing text outside of the elementSymbolRectangle. For example I would like to display the Elements Name, but I need it to be outside of the elementSymbolRectangle. I am new to programming and would appreciate any help.
Thanks
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self setAutoresizesSubviews:YES];
[self setupUserInterface];
// set the background color of the view to clearn
self.backgroundColor=[UIColor clearColor];
}
return self;
}
- (void)jumpToWikipedia:(id)sender {
// create the string that points to the correct Wikipedia page for the element name
NSString *wikiPageString = [NSString stringWithFormat:#"http://en.wikipedia.org/wiki/%#", self.element.name];
if (![[UIApplication sharedApplication] openURL:[NSURL URLWithString:wikiPageString]])
{
// there was an error trying to open the URL. for the moment we'll simply ignore it.
}
}
- (void)drawRect:(CGRect)rect {
// get the background image for the state of the element
// position it appropriately and draw the image
//
UIImage *backgroundImage = [self.element stateImageForAtomicElementView];
CGRect elementSymbolRectangle = CGRectMake(0, 0, [backgroundImage size].width, [backgroundImage size].height);
[backgroundImage drawInRect:elementSymbolRectangle];
// all the text is drawn in white
[[UIColor whiteColor] set];
// draw the element number
UIFont *font = [UIFont boldSystemFontOfSize:32];
CGPoint point = CGPointMake(10,5);
[[NSString stringWithFormat:#"%#", self.element.atomicNumber] drawAtPoint:point withFont:font];
// draw the element symbol
CGSize stringSize = [self.element.symbol sizeWithFont:font];
point = CGPointMake((self.bounds.size.width-stringSize.width-10),5);
[self.element.symbol drawAtPoint:point withFont:font];
This solved my problem.
UILabel *scoreLabel = [ [UILabel alloc ] initWithFrame:CGRectMake((self.bounds.size.width / 2), -50.0, 100.0, 100.0) ];
scoreLabel.textAlignment = UITextAlignmentCenter;
scoreLabel.textColor = [UIColor whiteColor];
scoreLabel.backgroundColor = [UIColor blackColor];
scoreLabel.font = [UIFont fontWithName:#"Arial Rounded MT Bold" size:(36.0)];
[self addSubview:scoreLabel];
scoreLabel.text = [NSString stringWithFormat: #"Test"];

How does one find a UIView... when there's no self.view?

So I've been fighting this one for a while as novice with iOS - I'm sure it's either a basic concept I'm missing, or a property I haven't run across yet that I need to reference.
Scenario: View controller creates a UIScrollView. UIView is created as a container for several UILabels (describing an event, venue and time). Method is called repeatedly to create these UILabels within the block. Creating these labels one by one works fine - simply adding each to the view - but when I move the code to a method and reuse it, abstracting such things as text size, indent, etc, I can't refer to the same parent view (because it's not a View Controller?), or search using viewWithTag (returns nothing) to find the parent.
Is this a simple fix, or is my basic structure flawed? Many thanks in advance for your time!
Header:
//
// ScheduleColumn.h
//
#import <Foundation/Foundation.h>
#interface ScheduleColumn : UIView {
}
- (void)makeTextBlock:(int)parentViewID label:(NSString*)label textSize:(int)textSize indent:(int)indent y:(int)y width:(int)width height:(int)height;
#end
Implementation:
//
// ScheduleColumn.m
//
#import "ScheduleColumn.h"
#implementation ScheduleColumn
// makeTextBlock: type, text, textSize, indent, build_y, width, height
// type: 0 = Title, 1 = Subtitle, 2 = Times
// text: Line content
// textSize: self-explanatory
// indent: indent from left side of parent
// build_y: # of units down from top of parent view to build
// width & height: self-explanatory
- (void)makeTextBlock:(int)parentViewID label:(NSString*)label textSize:(int)textSize indent:(int)indent y:(int)y width:(int)width height:(int)height {
double unixTime;
unixTime = [[NSDate date] timeIntervalSince1970];
NSLog(#"makeTextBlock called");
NSLog(#"parentViewID: %u", parentViewID);
NSLog(#"label: %#", label);
NSLog(#"textSize: %u", textSize);
NSLog(#"indent: %u", indent);
NSLog(#"y: %u", y);
NSLog(#"width: %u", width);
NSLog(#"height: %u", height);
NSLog(#"time: %u", unixTime);
UILabel *textView = [[UILabel alloc] initWithFrame:CGRectMake(indent, y, width, height)];
textView.backgroundColor = [UIColor clearColor];
textView.textColor = [UIColor whiteColor];
textView.lineBreakMode = UILineBreakModeWordWrap;
textView.numberOfLines = 0;
textView.tag = unixTime;
textView.font = [UIFont fontWithName:#"PetitaMedium" size: textSize];
textView.text = label;
CGSize constraintTextSize;
constraintTextSize.width = width;
constraintTextSize.height = MAXFLOAT;
CGSize theTextSize = [label sizeWithFont:[UIFont fontWithName:#"PetitaMedium" size: textSize] constrainedToSize:constraintTextSize lineBreakMode:UILineBreakModeWordWrap];
CGRect newTextFrame = textView.frame;
newTextFrame.size.height = theTextSize.height;
textView.frame = newTextFrame;
UIView *parentView = (UIView *)[self.view viewWithTag:parentViewID];
[parentView addSubview:textView];
[textView release];
NSLog(#"--- break ---");
}
.. and finally, the calls from the View Controller:
int build_y;
int subtitle_indent;
build_y = 30;
subtitle_indent = 20;
UIView *blockView = [[UIView alloc] initWithFrame: CGRectMake ( 0, build_y, 185, 50)];
blockView.tag = 100;
[FireworksContent addSubview:blockView];
// Add top line
UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, blockView.bounds.size.width, 0.5)];
lineView.backgroundColor = [UIColor whiteColor];
[blockView addSubview:lineView];
// Add Block Text
ScheduleColumn *blockText = [ScheduleColumn alloc];
[blockText makeTextBlock:blockView.tag label:#"Venue" textSize:18 indent:subtitle_indent y:build_y width:blockView.bounds.size.width height:20];
[blockText makeTextBlock:blockView.tag label:#"ShowTitle" textSize:12 indent:subtitle_indent y:build_y width:blockView.bounds.size.width height:20];
[blockText makeTextBlock:blockView.tag label:#"Showtime" textSize:36 indent:subtitle_indent y:build_y width:blockView.bounds.size.width height:20];
[lineView release];
[blockText release];
[blockView release];
... the viewWithTag line fails because "self" doesn't have a view... changing the class to a UIViewController lets it run, but still no joy.
A class method that returns a new view rather than an instance method that returns void would make more sense.
+(UIView *)makeTextBlock:(int)parentViewID label:(NSString*)label textSize:(int)textSize indent:(int)indent y:(int)y width:(int)width height:(int)height
Create the view as you want, and return that view at the end of the method.
Then you can create several of these views and hold a reference to them in your view controller if you want.
UIView *blockText1 = [ScheduleColumn makeTextBlock .....];
[self.view addSubview: blockText1];

Resources