Given the multitasking of iOS I thought it wouldn't be a pain to pause and resume my app, by pressing the home button or due to a phone call, but for a particular view controller it crashes.
The navigation bar is working fine i.e. when I tap "Back" it's ok, but if I try to tap controls of the displayed UI View Controller then I get EXC_BAD_ACCESS.. =/
Please find the code of my problematic View Controller below. I'm not very sure about it myself, because I used loadView to build its View. However apart from this interruption problem it works fine.
StoreViewController.h
#import <UIKit/UIKit.h>
#protocol StoreViewDelegate <NSObject>
#optional
- (void)DirectionsClicked:(double)lat:(double)lon;
#end
#interface StoreViewController : UIViewController
{
double latitude;
double longitude;
NSString *description;
NSString *imageURL;
short rating;
NSString *storeType;
NSString *offerType;
UIImageView *imageView;
UILabel *descriptionLabel;
id<StoreViewDelegate> storeViewDel;
}
#property (nonatomic) double latitude;
#property (nonatomic) double longitude;
#property (nonatomic) short rating;
#property (nonatomic,retain) NSString *description;
#property (nonatomic,retain) NSString *imageURL;
#property (nonatomic,retain) NSString *storeType;
#property (nonatomic,retain) NSString *offerType;
#property (assign) id<StoreViewDelegate> storeViewDel;
#end
StoreViewController.m
#import "StoreViewController.h"
#import <QuartzCore/QuartzCore.h>
#interface StoreViewController()
-(CGSize) calcLabelSize:(NSString *)string withFont:(UIFont *)font maxSize:(CGSize)maxSize;
#end
#implementation StoreViewController
#synthesize storeViewDel, longitude, latitude, description, imageURL, rating, offerType, storeType;
- (void)mapsButtonClicked:(id)sender
{
}
- (void)loadView
{
UIView *storeView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 0.0f, 0.0f)];
// Colours
UIColor *lightBlue = [[UIColor alloc] initWithRed:(189.0f / 255.0f)
green:(230.0f / 255.0f)
blue:(252.0f / 255.0f) alpha:1.0f];
UIColor *darkBlue = [[UIColor alloc] initWithRed:(28.0f/255.0f)
green:(157.0f/255.0f)
blue:(215.0f/255.0f)
alpha:1.0f];
// Layout
int width = self.navigationController.view.frame.size.width;
int height = self.navigationController.view.frame.size.height;
float firstRowHeight = 100.0f;
int margin = width / 20;
int imgWidth = (width - 3 * margin) / 2;
// Set ImageView
imageView = [[UIImageView alloc] initWithFrame:CGRectMake(margin, margin, imgWidth, imgWidth)];
CALayer *imgLayer = [imageView layer];
[imgLayer setMasksToBounds:YES];
[imgLayer setCornerRadius:10.0f];
[imgLayer setBorderWidth:4.0f];
[imgLayer setBorderColor:[lightBlue CGColor]];
// Load default image
NSData *imageData = [NSData dataWithContentsOfFile:#"thumb-null.png"];
UIImage *image = [UIImage imageWithData:imageData];
[imageView setImage:image];
[storeView addSubview:imageView];
// Set Rating
UIImageView *ratingView = [[UIImageView alloc] initWithFrame:CGRectMake(3 * width / 4 - 59.0f,
margin,
118.0f,
36.0f)];
UIImage *ratingImage = [UIImage imageNamed:#"bb-rating-0.png"];
[ratingView setImage:ratingImage];
[ratingImage release];
[storeView addSubview:ratingView];
// Set Get Directions button
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(3 * width / 4 - 71.5f,
36.0f + 2*margin,
143.0f, 63.0f)];
[btn addTarget:self action:#selector(mapsButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
UIImage *mapsImgUp = [UIImage imageNamed:#"bb-maps-up.png"];
[btn setImage:mapsImgUp forState:UIControlStateNormal];
[mapsImgUp release];
UIImage *mapsImgDown = [UIImage imageNamed:#"bb-maps-down.png"];
[btn setImage:mapsImgDown forState:UIControlStateHighlighted];
[mapsImgDown release];
[storeView addSubview:btn];
[btn release];
// Set Description Text
UIScrollView *descriptionView = [[UIScrollView alloc] initWithFrame:CGRectMake(0.0f,
imgWidth + 2 * margin,
width,
height - firstRowHeight)];
descriptionView.backgroundColor = lightBlue;
CGSize s = [self calcLabelSize:description withFont:[UIFont systemFontOfSize:18.0f] maxSize:CGSizeMake(width, 9999.0f)];
descriptionLabel = [[UILabel alloc] initWithFrame:CGRectMake(margin, margin, width - 2 * margin, s.height)];
descriptionLabel.lineBreakMode = UILineBreakModeWordWrap;
descriptionLabel.numberOfLines = 0;
descriptionLabel.font = [UIFont systemFontOfSize:18.0f];
descriptionLabel.textColor = darkBlue;
descriptionLabel.text = description;
descriptionLabel.backgroundColor = lightBlue;
[descriptionView addSubview:descriptionLabel];
[storeView addSubview:descriptionView];
[descriptionLabel release];
[lightBlue release];
[darkBlue release];
self.view = storeView;
[storeView release];
}
- (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.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc
{
[imageView release];
[descriptionLabel release];
[super dealloc];
}
#end
Any suggestions ?
Thank you,
F.
Perhaps look at your crash report, get an idea of where it's crashing, and help us help you by posting the relevant code if it is not clear to you what is causing the crash. We may be able to help you further, but we need you to do some pre-requisite work first, like trying to solve it yourself with the appropriate information at hand. Now that you know what to look for, please, go forth and unicorn!
The problem was that I released some UIImages that were never allocated. Erasing the following lines solved my issue:
[ratingImage release];
[mapsImgUp release];
[mapsImgDown release];
F.
Related
I am slowly developing a custom button (while learning how to implement classes etc).
I have a ViewController, which imports a SuperButton.h (UIControl) and then creates an instance of the SuperButton. (This works, as proved by a NSLog.)
But I cannot get the method in SuperButton to display a Label.
I think this might have something to with the '.center' value or the 'addSubview' command?
I would really appreciate your help. Thanks.
Here is my SuperButton.m code:
#import "SuperButton.h"
#implementation SuperButton
#synthesize firstTitle;
#synthesize myLabel;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void) shoutName{
NSLog(#"My name is %#", firstTitle);
self.backgroundColor = [UIColor blueColor];
CGRect labelFrame = CGRectMake(0.0f, 0.0f, 100.0f, 50.0f);
self.myLabel = [[UILabel alloc] initWithFrame:labelFrame];
self.myLabel.text = #"Come on, don't be shy.";
self.myLabel.font = [UIFont italicSystemFontOfSize:14.0f];
self.myLabel.textColor = [UIColor grayColor];
self.myLabel.center = self.center;
[self addSubview:self.myLabel];
}
Here is the code in my ViewController:
- (void) makeButton{
SuperButton *button1 = [[SuperButton alloc] init];
button1.firstTitle = #"Mr. Ploppy";
[button1 shoutName];
}
(EDIT:) Just in case, here's the SuperButton.h code:
#import <UIKit/UIKit.h>
#interface SuperButton : UIControl
#property (nonatomic, strong) NSString *firstTitle;
#property (nonatomic, strong) UILabel *myLabel;
- (void) shoutName;
#end
I found an answer elsewhere.
I needed to addSubview the 'button'. My working code now looks like this:
- (void) makeButton{
SuperButton *button1 = [[SuperButton alloc] init];
button1.firstTitle = #"Mr. Ploppy";
[button1 shoutName];
[self.view addSubview:button1.myLabel];
[self.view sendSubviewToBack:button1.myLabel];
}
You are initializing your button not with initWithFrame: method, but with simple init. This makes the button of CGRectZero size. Change this line:
SuperButton *button1 = [[SuperButton alloc] init];
to this:
SuperButton *button1 = [[SuperButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 50.0f)];
or add a call to setFrame: after initializing your button.
I am another rookie in iPhone programming and I have a very basic question which I can not answer.
First of all, I am writing code in xcode 3.1.4, so that I can learn the basics on an old platform and hopefully in the near future this will allow me to create portable apps (in the sense that I will be supporting iPhone 3 as well). Unfortunately though, from what I understand, apple has stripped the documentation that came with xcode 3.1.4 and this makes it hard to guess what is the right way of doing things, since the current documentation and examples suggest routes more relevant to iOS 5 (e.g. storyboard). So, even though I appear to understand the whole concept of MVC, and although my first real toy app is actually functioning it is certainly not managing the memory correctly as the relevant dealloc methods are not used (the relevant NSLogs that I have are not shown). For the learning process I am following the course material that is online from CS193P in Winter of 2010.
To the specifics now.
I want to create a single-window app (Polygon, Assignment 3). Let's forget about the interface builder; all views/subviews will be created programmatically. For that I create a custom UIViewController. I also create a custom UIView in order to describe a custom subview of my single view that I want to present. (It is very likely that we disagree with my approach here already but for now I seriously believe that what I am trying here is conceptually right). In the app delegate (.h) I declare my mainController and this is all I really declare. The file is the following:
#import <UIKit/UIKit.h>
#import "MainViewController.h"
#interface MySimpleHelloPolyAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
MainViewController * mainController;
}
#property (nonatomic, retain) IBOutlet UIWindow * window;
#property (nonatomic, assign) IBOutlet MainViewController * mainController;
#end
On runtime, I want mainController to allocate the necessary memory for the main view presented to the user and also add a subview of my custom UIView (this will be the canvas for drawing polygons). So, essentially I want to treat window as a container, the view of mainController to actually present the main view to the user, and to that view I will also have attached a subview with an instance of our custom canvas (custom view). Below is the AppDelegate.m file:
#import "MySimpleHelloPolyAppDelegate.h"
#implementation MySimpleHelloPolyAppDelegate
#synthesize window, mainController;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[window setBackgroundColor:[UIColor blueColor]]; // I do not want to see 'blue' on runtime
// On the creation of the MainViewController subclass I indicated that I want a nib for my new controller
// simply because this is the recommended way by apple. Apparently this should be an overkill for a simple
// one-window app, but nevertheless it should be ok.
mainController = [[MainViewController alloc] initWithNibName:nil bundle:nil];
[window addSubview:mainController.view];
[window makeKeyAndVisible];
}
- (void)dealloc {
NSLog(#"(AppDelegate): Dealloc is called");
[mainController.view removeFromSuperview];
[mainController release];
[window release];
[super dealloc];
}
#end
Let's see the MainViewController.h:
#import <UIKit/UIKit.h>
#import "Polygon.h"
#import "Canvas.h"
#interface MainViewController : UIViewController {
IBOutlet UILabel * numSidesTextLabel;
IBOutlet UILabel * numSidesValueLabel;
IBOutlet UILabel * advancedOptionsLabel;
IBOutlet UILabel * polygonNameLabel;
IBOutlet UISwitch * advancedOptionsSwitch; // Not supported yet
IBOutlet UIButton * increaseButton;
IBOutlet UIButton * decreaseButton;
IBOutlet Canvas * myCanvas;
Polygon * myPolygon;
}
#property (nonatomic, retain) IBOutlet UILabel * numSidesTextLabel;
#property (nonatomic, retain) IBOutlet UILabel * numSidesValueLabel;
#property (nonatomic, retain) IBOutlet UIButton * increaseButton;
#property (nonatomic, retain) IBOutlet UIButton * decreaseButton;
#property (nonatomic, retain) IBOutlet UIView * myCanvas;
#property (nonatomic, retain) IBOutlet Polygon * myPolygon;
- (IBAction) increase;
- (IBAction) decrease;
- (void) updateInterface;
#end
Now let's see the MainViewController.m:
#import "MainViewController.h"
#implementation MainViewController
#synthesize numSidesTextLabel, numSidesValueLabel, decreaseButton, increaseButton, myCanvas, myPolygon;
- (IBAction) decrease {
[myPolygon setNumberOfSides:([myPolygon numberOfSides]-1)];
[self updateInterface];
}
- (IBAction) increase {
[myPolygon setNumberOfSides:([myPolygon numberOfSides]+1)];
[self updateInterface];
}
- (void) updateInterface {
int sides = [myPolygon numberOfSides];
UIColor * myOceanColor = [UIColor colorWithRed:0.00 green:0.333 blue:0.557 alpha:1.00];
[increaseButton setTitleColor:myOceanColor forState:UIControlStateNormal];
[increaseButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateDisabled];
[decreaseButton setTitleColor:myOceanColor forState:UIControlStateNormal];
[decreaseButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateDisabled];
numSidesValueLabel.text = [NSString stringWithFormat:#"%d", sides];
decreaseButton.enabled = YES; increaseButton.enabled = YES;
if (sides == MAX_NUM_SIDES_CONSTANT) increaseButton.enabled = NO;
else if (sides == MIN_NUM_SIDES_CONSTANT) decreaseButton.enabled = NO;
[myCanvas updateState:sides withName:[myPolygon name]];
[myCanvas setNeedsDisplay];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// Custom initialization
}
return self;
}
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:CGRectMake(0.0, 20.0, 320.0, 460.0)];
self.view.backgroundColor = [UIColor whiteColor];
}
- (void)viewDidLoad {
CGRect tempFrame;
UIColor * oceanColor = [UIColor colorWithRed:0.000 green:0.333 blue:0.557 alpha:1.000];
// Draw the labels
tempFrame = CGRectMake(20.0, 20.0, 150.0, 20.0);
numSidesTextLabel = [[UILabel alloc] initWithFrame:tempFrame];
numSidesTextLabel.text = #"Number of sides:";
numSidesTextLabel.textAlignment = UITextAlignmentLeft;
[self.view addSubview:numSidesTextLabel];
[numSidesTextLabel release];
tempFrame = CGRectMake(160.0, 20.0, 40.0, 20.0);
numSidesValueLabel = [[UILabel alloc] initWithFrame:tempFrame];
numSidesValueLabel.text = #"-----";
numSidesValueLabel.textAlignment = UITextAlignmentLeft;
[self.view addSubview:numSidesValueLabel];
[numSidesValueLabel release];
tempFrame = CGRectMake(20.0, 125.0, 160.0, 20.0);
advancedOptionsLabel = [[UILabel alloc] initWithFrame:tempFrame];
advancedOptionsLabel.text = #"Advanced options";
advancedOptionsLabel.textAlignment = UITextAlignmentLeft;
[self.view addSubview:advancedOptionsLabel];
[advancedOptionsLabel release];
// Draw the advanced switch
tempFrame = CGRectMake(205.0, 120.0, 120.0, 60.0);
advancedOptionsSwitch = [[UISwitch alloc] initWithFrame:tempFrame];
[advancedOptionsSwitch setOn:NO animated:YES];
[self.view addSubview:advancedOptionsSwitch];
[advancedOptionsSwitch release];
// Decrease Button
decreaseButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; // autoreleased
[decreaseButton setTitle:#"Decrease" forState:UIControlStateNormal];
[decreaseButton setTitleColor:oceanColor forState:UIControlStateNormal];
[decreaseButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateDisabled];
decreaseButton.frame = CGRectMake(20.0, 60.0, 110.0, 40.0);
[decreaseButton addTarget:self action:#selector(decrease) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:decreaseButton];
// Increase Button
increaseButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; // autoreleased
[increaseButton setTitle:#"Increase" forState:UIControlStateNormal];
[increaseButton setTitleColor:oceanColor forState:UIControlStateNormal];
[increaseButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateDisabled];
increaseButton.frame = CGRectMake(190.0, 60.0, 110.0, 40.0);
[increaseButton addTarget:self action:#selector(increase) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:increaseButton];
// Initialize the polygon
myPolygon = [[Polygon alloc] init];
numSidesValueLabel.text = [NSString stringWithFormat:#"%d", [myPolygon numberOfSides]];
// Prepare the canvas
CGRect myCanvasFrame = CGRectMake(20.0, 160.0, 280.0, 280.0);
myCanvas = [[Canvas alloc] initWithFrame:myCanvasFrame withNumSides:[myPolygon numberOfSides] withPolygonName:[myPolygon name]];
[self.view addSubview:myCanvas];
[myCanvas release];
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
numSidesTextLabel = nil;
numSidesValueLabel = nil;
advancedOptionsLabel = nil;
myPolygon = nil;
myCanvas = nil;
[self.view release]; self.view = nil;
}
- (void)dealloc {
NSLog(#"(MainViewController): Dealloc is called");
[numSidesTextLabel removeFromSuperview];
[numSidesValueLabel removeFromSuperview];
[advancedOptionsLabel removeFromSuperview];
[myPolygon release];
[myCanvas removeFromSuperview]; // This should generate additional output
[super dealloc];
}
#end
Now let's have a look on the Canvas.h file:
#import <UIKit/UIKit.h>
#import "PrepDefs.h"
#interface Canvas : UIView {
IBOutlet UILabel * polygonNameLabel;
int currentStateNumSides;
CGPoint center;
}
#property (nonatomic, retain) UILabel * polygonNameLabel;
#property (nonatomic, assign) int currentStateNumSides;
#property (nonatomic, assign) CGPoint center;
+ (NSArray *) pointsForPolygonInRect:(CGRect) rect numberOfSides:(int) numberOfSides;
- (id) initWithFrame:(CGRect)frame withNumSides:(int)sides withPolygonName:(NSString *) name;
- (void) updateState:(int)sides withName:(NSString *)name;
#end
The Canvas.m file:
#import "Canvas.h"
#implementation Canvas
#synthesize polygonNameLabel, currentStateNumSides, center;
+ (NSArray *) pointsForPolygonInRect:(CGRect)rect numberOfSides:(int)numberOfSides {
CGPoint center = CGPointMake(rect.size.width / 2.0, rect.size.height / 2.0);
float radius = 0.9 * center.x;
NSMutableArray *result = [NSMutableArray array];
float angle = (2.0 * M_PI) / numberOfSides;
float exteriorAngle = M_PI - angle;
float rotationDelta = angle - (0.5 * exteriorAngle);
for (int currentAngle = 0; currentAngle < numberOfSides; currentAngle++) {
float newAngle = (angle * currentAngle) - rotationDelta;
float curX = cos(newAngle) * radius;
float curY = sin(newAngle) * radius;
[result addObject:[NSValue valueWithCGPoint:CGPointMake(center.x + curX, center.y + curY)]];
}
return result;
}
- (id) initWithFrame:(CGRect)frame withNumSides:(int)sides withPolygonName:(NSString *)name {
if (self = [super initWithFrame:frame]) {
// Initialization code
[self setBackgroundColor:[UIColor brownColor]];
currentStateNumSides = sides;
center = CGPointMake ([self bounds].size.width / 2.0, [self bounds].size.height / 2.0);
polygonNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, center.y - 10.0, [self bounds].size.width, 20.0)];
polygonNameLabel.text = name;
polygonNameLabel.textAlignment = UITextAlignmentCenter;
polygonNameLabel.backgroundColor = [UIColor clearColor];
[self addSubview:polygonNameLabel];
[polygonNameLabel release];
}
return self;
}
- (id)initWithFrame:(CGRect)frame {
NSLog(#"(Canvas, -initWithFrame): You should always use initWithFrame:withNumSides:withPolygonName:");
return [self initWithFrame:frame withNumSides:DEFAULT_NUM_SIDES withPolygonName:DEFAULT_POLYGON_NAME];
}
- (void)drawRect:(CGRect)rect {
// Drawing code
NSArray * points = [Canvas pointsForPolygonInRect:[self bounds] numberOfSides:[self currentStateNumSides]];
CGContextRef currentContext = UIGraphicsGetCurrentContext();
[[UIColor blackColor] set];
UIRectFrame ([self bounds]);
CGContextBeginPath (currentContext);
for (int i = 0; i < [points count]; i++) {
CGPoint currentPoint;
NSValue * currentValue;
currentValue = [points objectAtIndex:i];
currentPoint = [currentValue CGPointValue];
//NSLog(#"(%.1f, %.1f)", currentPoint.x, currentPoint.y);
if (i == 0)
CGContextMoveToPoint(currentContext, currentPoint.x, currentPoint.y);
else
CGContextAddLineToPoint(currentContext, currentPoint.x, currentPoint.y);
}
CGContextClosePath(currentContext);
[[UIColor yellowColor] setFill];
[[UIColor blackColor] setStroke];
CGContextDrawPath(currentContext, kCGPathFillStroke);
}
- (void) updateState:(int)sides withName:(NSString *)name {
currentStateNumSides = sides;
polygonNameLabel.text = name;
}
- (void)dealloc {
NSLog(#"(Canvas): Calling dealloc");
[polygonNameLabel removeFromSuperview]; polygonNameLabel = nil;
[super dealloc];
}
#end
The PrepDefs.h file:
#ifndef __PREP_DEFS_H__
#define __PREP_DEFS_H__
#define MIN_NUM_SIDES_CONSTANT 3
#define DEFAULT_NUM_SIDES 5
#define MAX_NUM_SIDES_CONSTANT 12
#define DEFAULT_POLYGON_NAME #"Pentagon"
#endif
Finally the files for the Polygon class. The Polygon.h file:
#import <Foundation/Foundation.h>
#import "PrepDefs.h"
#interface Polygon : NSObject {
int numberOfSides;
int minimumNumberOfSides;
int maximumNumberOfSides;
}
#property int numberOfSides;
#property int minimumNumberOfSides;
#property int maximumNumberOfSides;
#property (readonly) float angleInDegrees;
#property (readonly) float angleInRadians;
#property (readonly) NSString * name;
- (void) setNumberOfSides:(int)numSides;
- (void) setMinimumNumberOfSides:(int)minNumSides;
- (void) setMaximumNumberOfSides:(int)maxNumSides;
- (id) initWithNumberOfSides:(int)sides minimumNumberOfSides:(int)min maximumNumberOfSides:(int)max;
#end
The Polygon.m file:
#import "Polygon.h"
#implementation Polygon
#synthesize numberOfSides, minimumNumberOfSides, maximumNumberOfSides, angleInDegrees, angleInRadians, name;
- (void) setNumberOfSides:(int)numSides {
if ((numSides >= [self minimumNumberOfSides]) && (numSides <= [self maximumNumberOfSides])) numberOfSides = numSides;
else NSLog(#"PolygonShape [setNumberOfSides]: Assignment out of bounds");
}
- (void) setMinimumNumberOfSides:(int)minNumSides {
if (minNumSides >= MIN_NUM_SIDES_CONSTANT) minimumNumberOfSides = minNumSides;
else NSLog(#"PolygonShape [setMinimumNumberOfSides]: Assignment out of bounds");
}
- (void) setMaximumNumberOfSides:(int)maxNumSides {
if (maxNumSides <= MAX_NUM_SIDES_CONSTANT) maximumNumberOfSides = maxNumSides;
else NSLog(#"PolygonShape [setMaximumNumberOfSides]: Assignment out of bounds");
}
- (float) angleInDegrees {
int sides = [self numberOfSides];
return ((float) (180.0 * ((double) (sides - 2)) / ((double) sides)));
}
- (float) angleInRadians {
int sides = [self numberOfSides];
return ((double) M_PI) * ((double) (sides - 2.0)) / ((double) sides);
}
- (NSString *) name {
NSString * s;
switch ([self numberOfSides]) {
case 3: s = [NSString stringWithString:#"Triangle"]; break;
case 4: s = [NSString stringWithString:#"Square"]; break;
case 5: s = [NSString stringWithString:#"Pentagon"]; break;
case 6: s = [NSString stringWithString:#"Hexagon"]; break;
case 7: s = [NSString stringWithString:#"Heptagon"]; break;
case 8: s = [NSString stringWithString:#"Octagon"]; break;
case 9: s = [NSString stringWithString:#"Enneagon"]; break;
case 10: s = [NSString stringWithString:#"Decagon"]; break;
case 11: s = [NSString stringWithString:#"Hendecagon"]; break;
case 12: s = [NSString stringWithString:#"Dodecagon"]; break;
default: NSLog(#"PolygonShape [name]: I should never enter here."); s = nil; break;
}
return s;
}
- (id) init {
return [self initWithNumberOfSides:DEFAULT_NUM_SIDES minimumNumberOfSides:MIN_NUM_SIDES_CONSTANT maximumNumberOfSides:MAX_NUM_SIDES_CONSTANT];
}
- (id) initWithNumberOfSides:(int)sides minimumNumberOfSides:(int)min maximumNumberOfSides:(int)max {
if (self = [super init]) {
// Cautiously initialize everything to zero
numberOfSides = 0;
minimumNumberOfSides = 0;
maximumNumberOfSides = 0;
// Attempt the actual assignment
[self setMaximumNumberOfSides:max];
[self setMinimumNumberOfSides:min];
[self setNumberOfSides:sides];
}
return self;
}
- (NSString *) description {
return [NSString stringWithFormat:#"Hello I am a %d-sided polygon (aka a %#) with angles of %.3f degrees (%.6f radians)", [self numberOfSides], [self name], [self angleInDegrees], [self angleInRadians]];
}
- (void) dealloc {
NSLog(#"(Polygon): Dealloc is called");
[super dealloc];
}
#end
After all the above, I believe the questions are:
Why dealloc functions are not used? Where do I actually have leaks?
When I initialize my mainController, should I pass as parameter the name of the nib file that was created when I created the custom class for the mainController?
Should I initialize the mainController and its view differently?
Should I use awakeFromNib instead of loadView or viewDidLoad?
Other comments that you have regarding the entire approach? Such as good or bad programming practices for one-screen applications? It really is a small program, so, I believe this is not an open-ended question. If however you think that this is an open-ended question, feel free not to answer. This is probably a naive question by a rookie to some veterans.
I really appreciate all the time that you spend on this and I am looking forward to your feedback.
How to achieve below effect??
I want that when my UIImage width is smaller then UILabel width then my image should be displayed only once as below.
I already have done some more work with UILabel and UIImage. Refer to my previous question : How to stretch Image to fill the Label Width set in Background in UILabel?.
But now i want some more fun with UILabel... :D
EDIT :
SBLabel *lbl = [[SBLabel alloc] initWithFrame:CGRectMake(0, 50, 320, 28)];
lbl.text = #"Hello World!!!...";
UIImage *img = [UIImage imageNamed:#"cn3.png"];
UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 50, 320, 28)];
imgView.image = img;
[lbl setNonRepeatingBackgroundImage:imgView];
[self.view addSubview:lbl];
[imgView release];
if (image.size.width < label.size.width ||image.size.height < label.size.height )
{
//rect here is frame of image
[img drawInRect:rect];
label.backgroudColor = [UIColor clearColor];
}
It seems like the most straightforward solution would be to have your UIImage sit behind your UILabel and your UILabel has a transparent background color.
Edit
Assuming you're using ARC...
SBLabel.h
#import <UIKit/UIKit.h>
#interface SBLabel : UILabel
#property (nonatomic, strong) UIImageView *nonRepeatingBackgroundImage;
#end
SBLabel.m
#import "SBLabel.h"
#implementation SBLabel
#synthesize nonRepeatingBackgroundImage;
- (void)setNonRepeatingBackgroundImage:(UIImageView *)aNonRepeatingBackgroundImage {
nonRepeatingBackgroundImage = aNonRepeatingBackgroundImage;
[self addSubview:aNonRepeatingBackgroundImage];
[self sendSubviewToBack:aNonRepeatingBackgroundImage];
[self setBackgroundColor:[UIColor clearColor]];
}
#end
You would need to call this setter method after you add the UILabel to the parent view. I have not tested but I believe it should work, might require some tweaks but hopefully it explains how to Subclass UILabel to make a custom enhancement such as this.
A subclass of UILabel could look like this (without ARC)
CustomLabel.h
#import <UIKit/UIKit.h>
#interface CustomLabel : UILabel {
UIImage *mImage;
}
#property (retain) UIImage *image;
#end
CustomLabel.m
#import "CustomLabel.h"
#implementation CustomLabel
- (void)dealloc {
self.image = nil;
[super dealloc];
}
- (void)drawRect:(CGRect)rect {
[mImage drawAtPoint:CGPointMake(0, 0)];
[super drawRect:rect];
}
- (UIImage*)image {
return mImage;
}
- (void)setImage:(UIImage *)image {
self.backgroundColor = [UIColor clearColor];
if(mImage)
[mImage release];
mImage = [image retain];
[self setNeedsDisplay];
}
#end
Usage
yourCustomLabel.image = [UIImage imageNamed:#"test.png"];
I have the following code which works fine UIScrollView. But when loading the picture can be seen only at the last subview and UIActivityIndicatorView not working for loading each picture.
How to be done for such a task?
myProject2ViewController.h
#import <UIKit/UIKit.h>
#import "PageScrollView.h"
#interface myProject2ViewController : UIViewController
{
NSMutableArray *pages;
PageScrollView *scrollView;
UIView *myOneSubview;
UIImageView *imageView;
UIActivityIndicatorView *myIndicator;
}
#property (nonatomic, retain) PageScrollView *scrollView;
#property (nonatomic, retain) UIImageView *imageView;
#property (nonatomic, retain) UIActivityIndicatorView *myIndicator;
#end
myProject2ViewController.m
#import "myProject2ViewController.h"
#implementation myProject2ViewController
#synthesize scrollView;
#synthesize imageView;
#synthesize myIndicator;
- (void)dealloc
{
[super dealloc];
}
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *imageUrls = [NSArray arrayWithObjects:
#"link_1",
#"link_2",
#"link_3",
nil];
pages = [[NSMutableArray alloc] init];
for (int i = 0; i < [imageUrls count]; i++) {
CGRect frameOne = CGRectMake(0.0f, 50.0f, 320.0f, 416.0f);
myOneSubview = [[UIView alloc] initWithFrame:frameOne];
myOneSubview.backgroundColor = [UIColor blackColor];
NSString *imageUrl = [imageUrls objectAtIndex:i];
myIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
myIndicator.center = CGPointMake(160.0f, 130.0f);
myIndicator.hidesWhenStopped = YES;
myIndicator.backgroundColor = [UIColor clearColor];
imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 50.0f, 320.0f, 416.0f)];
[NSThread detachNewThreadSelector:#selector(loadImages:) toTarget:self withObject:imageUrl];
[myOneSubview addSubview:imageView];
[pages addObject:myOneSubview];
}
scrollView = [[PageScrollView alloc] initWithFrame:self.view.frame];
scrollView.pages = pages;
scrollView.delegate = self;
self.view = scrollView;
}
- (void)loadImages:(NSString *)string
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[myIndicator startAnimating];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:string]];
UIImage *image = [[UIImage alloc] initWithData:imageData];
[imageView setImage:image];
[image release];
[imageData release];
[self performSelectorOnMainThread:#selector(loadImageComplete) withObject:self waitUntilDone:YES];
[pool drain];
}
-(void)loadImageComplete
{
NSLog(#"Load image complete!");
[myIndicator stopAnimating];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
#end
When you do:
CGRectMake(0.0f, 50.0f, 320.0f, 416.0f);
Inside your for loop, you're specifying those same exact position and dimension for EVERY image you create in the for loop. In other words, every next image you create will sit ontop of the previous image, as a result, you will only see the final image you created in the for loop.
Try adding your counter "i" variable to the X and Y value in CGRectMake() to offset every image that gets subsequently created.
So let say each image was 100 pixel by 100 pixel, then something like:
CGRectMake(0.0f + (i * 100.0f), 50.0f, 320.0f, 416.0f);
Would make each image sit right after the previous one (since each image is 100 pixel wide, we offset it by 100 pixel and use that value as the starting point of the next image). If you add an extra value after (i * 100) so it becomes (i * 100 + 50) then that would give you a left padding of 50 pixels from the previous image.
If your images are different dimensions you'll need to calculate the width and height of the image before hand and factor those into your CGRectMake() method.
Hope that helps.
In addition, instead of creating so many views to add to your scroll view, you could write the code like so:
scrollView = [[UIScrollview alloc] init];
. . .
for(...)
{
UIImageView *currentImage = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f + (i * 100.0f), 50.0f, 320.0f, 416.0f);
[currentImage setImage:[UIImage imageNamed:[imageUrls objectAtIndex:i]]];
. . .
[scrollView addSubview: currentImage];
[currentImage release];
}
I'm having a memory problem related to UIImageView. After adding this view to my UIScrollView, if I try to release the UIImageView the application crashes. According to the stack trace, something is calling [UIImageView stopAnimating] after [UIImageView dealloc] is called. However, if I don't release the view the memory is never freed up, and I've confirmed that there remains an extra retain call on the view after deallocating...which causes my total allocations to climb quickly and eventually crash the app after loading the view multiple times. I'm not sure what I'm doing wrong here though...I don't know what is trying to access the UIImageView after it has been released. I've included the relevant header and implementation code below (I'm using the Three20 framework, if that has anything to do with it...also, AppScrollView is just a UIScrollView that forwards the touchesEnded event to the next responder):
Header:
#interface PhotoHiResPreviewController : TTViewController <UIScrollViewDelegate> {
NSString* imageURL;
UIImage* hiResImage;
UIImageView* imageView;
UIView* mainView;
AppScrollView* mainScrollView;
}
#property (nonatomic, retain) NSString* imageURL;
#property (nonatomic, retain) NSString* imageShortURL;
#property (nonatomic, retain) UIImage* hiResImage;
#property (nonatomic, retain) UIImageView* imageView;
- (id)initWithImageURL:(NSString*)imageTTURL;
Implementation:
#implementation PhotoHiResPreviewController
#synthesize imageURL, hiResImage, imageView;
- (id)initWithImageURL:(NSString*)imageTTURL {
if (self = [super init]) {
hiResImage = nil;
NSString *documentsDirectory = [NSString stringWithString:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]];
[self setImageURL:[NSString stringWithFormat:#"%#/%#.jpg", documentsDirectory, imageTTURL]];
}
return self;
}
- (void)loadView {
[super loadView];
// Initialize the scroll view
hiResImage = [UIImage imageWithContentsOfFile:self.imageURL];
CGSize photoSize = [hiResImage size];
mainScrollView = [[AppScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
mainScrollView.autoresizingMask = ( UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
mainScrollView.backgroundColor = [UIColor blackColor];
mainScrollView.contentSize = photoSize;
mainScrollView.contentMode = UIViewContentModeScaleAspectFit;
mainScrollView.delegate = self;
// Create the image view and add it to the scrollview.
UIImageView *tempImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, photoSize.width, photoSize.height)];
tempImageView.contentMode = UIViewContentModeCenter;
[tempImageView setImage:hiResImage];
self.imageView = tempImageView;
[tempImageView release];
[mainScrollView addSubview:imageView];
// Configure zooming.
CGSize screenSize = [[UIScreen mainScreen] bounds].size;
CGFloat widthRatio = screenSize.width / photoSize.width;
CGFloat heightRatio = screenSize.height / photoSize.height;
CGFloat initialZoom = (widthRatio > heightRatio) ? heightRatio : widthRatio;
mainScrollView.maximumZoomScale = 3.0;
mainScrollView.minimumZoomScale = initialZoom;
mainScrollView.zoomScale = initialZoom;
mainScrollView.bouncesZoom = YES;
mainView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
mainView.autoresizingMask = ( UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
mainView.backgroundColor = [UIColor blackColor];
mainView.contentMode = UIViewContentModeScaleAspectFit;
[mainView addSubview:mainScrollView];
// Add to view
self.view = mainView;
[imageView release];
[mainScrollView release];
[mainView release];
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return imageView;
}
- (void)dealloc {
mainScrollView.delegate = nil;
TT_RELEASE_SAFELY(imageURL);
TT_RELEASE_SAFELY(hiResImage);
[super dealloc];
}
I'm not sure how to get around this. If I remove the call to [imageView release] at the end of the loadView method everything works fine...but I have massive allocations that quickly climb to a breaking point. If I DO release it, however, there's that [UIImageView stopAnimating] call that crashes the application after the view is deallocated.
Thanks for any help! I've been banging my head against this one for days. :-P
Cheers,
Josiah
Move the release of imageView before the point where you set it. This way you release the object before changing where the imageView pointer points to:
// Create the image view and add it to the scrollview.
UIImageView *tempImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, photoSize.width, photoSize.height)];
tempImageView.contentMode = UIViewContentModeCenter;
[tempImageView setImage:hiResImage];
// release old object
[imageView release];
// set the pointer to point at a new object
self.imageView = tempImageView;
[tempImageView release];
[mainScrollView addSubview:imageView];
Then at the bottom of your method where you release the other objects, remove the line:
[imageView release];
Ideally, class level variables should be released in the class' dealloc() method
Looks like imgView is a class level variable and you are releasing that at the end of the method call.
If you have a variable that you would like to release at the end of the method -- it should be a method-level variable. So, in your case, try making the imgView a method level variable. Then you should be able to release it at the end of the method without any problem
Why do you create a temporary view? Do you call loadView more than once?
self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, photoSize.width, photoSize.height)];
[self.imageView setContentMode:UIViewContentModeCenter];
[self.imageView setImage:hiResImage];
[mainScrollView addSubview:self.imageView];
[self.imageView release]
Or release it in dealloc since it's an instance variable? addSubview retains your UIImageView, so it would be weird if it crashed because the object isn't present. Have you tried setting out breakpoints?