I'm practicing on how TabViewcontroller works. Now I have 2 subclasses of UIViewcontroller.
One is HypnosisViewController , the other is TimeViewController.
What I wanted to check is how -(void)viewDidLoad works when IOS simulator gets memory warning.
And I did
Built and ran the app
The console said "HypnosisViewcontroller loaded its view."
Switched the other tab (TimeViewController)
Saw the message in the console. It says "TabViewcontroller loaded its view"
Did the simulator memory warning command in IOS simulator
The console said "HypnoTime Received memory warning."
Switched back to the HypnosisViewcontroller to see whether the console says "HypnosisViewcontroller loaded its view." again.
So the problem here is HypnosisViewcontroller is not destroyed and created again. (Because I can't see the log message when I switch back to HypnosisViewcontroller.)However I leaned the view not on the screen should be destroyed during the memory warning.
Did I miss something? Thanks in advance!
HypnosisViewController.m:
#import "HypnosisViewController.h"
#import "HypnosisView.h"
#implementation HypnosisViewController
-(void)loadView
{
//Create a view
CGRect frame = [[UIScreen mainScreen] bounds];
HypnosisView *v = [[HypnosisView alloc] initWithFrame:frame];
// Set it as *the* view of this view controller
[self setView:v];
}
-(id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle
{
self = [super initWithNibName:nil
bundle:nil];
if(self){
//Get the tab bar item
UITabBarItem *tbi = [self tabBarItem];
//Give it a label
[tbi setTitle:#"Hypnosis"];
//Create a UIImage from a file
//This will use Hypno#2x.png on retina display devices
UIImage *i = [UIImage imageNamed:#"Hypno.png"];
// Put that image on the tab bar item
[tbi setImage:i];
}
return self;
}
-(void)viewDidLoad
{
// Always call the super implmetaion of viewDidload
[super viewDidLoad];
NSLog(#"HypnosisViewcontroller loaded its view");
}
#end
TimeViewController.m:
#import "TimeViewController.h"
#implementation TimeViewController
-(IBAction)showCurrentTime:(id)sender
{
NSDate *now = [NSDate date];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setTimeStyle:NSDateFormatterMediumStyle];
[timeLabel setText:[formatter stringFromDate:now]];
[timeLabel2 setText:[formatter stringFromDate:now]];
}
-(id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle
{
// Call the superclass's designated initializer
self = [super initWithNibName:nil
bundle:nil];
//Get a pointer to the application bundle object
// NSBundle *appBundle = [NSBundle mainBundle];
// self = [super initWithNibName:#"TimeViewController"
//bundle:appBundle];
if(self){
//Get the tab bar item
UITabBarItem *tbi = [self tabBarItem];
//Give it a label
[tbi setTitle:#"Time"];
//Create a UIImage from a file
//This will use Time#2x.png on retina display devices
UIImage *i = [UIImage imageNamed:#"Time.png"];
// Put that image on the tab bar item
[tbi setImage:i];
}
return self;
}
-(void)viewDidLoad
{
// Always call the super implmetaion of viewDidload
[super viewDidLoad];
NSLog(#"TimeViewcontroller loaded its view");
// [[self view] setBackgroundColor:[UIColor greenColor]];
}
#end
Memory Warnings don't cause the Controllers to destroy/unload their views anymore.
It is working properly. And HypnosisViewcontroller was destroyed and created again, because viewDidLoad will be called only when all the views are initiated. So here you see the log message again when you switch back to HypnosisViewcontroller which represent that HypnosisViewcontroller has been purged from memory and initiated again. You can try switch between these two view controllers without simulating memory warning, and you will only see the log message once.
Related
Novice iOS programmer here, so sorry if I'm missing something easy, but...
I have a UIViewController class called LVSTSPMasterViewController whose view was built in IB. The view contains a UIScrollView (added in IB) and that scroll view has a subview of type LVSTSPView. LVSTSPView has a controller of type LVSTSPViewController.
I want to respond to touches in the LVSTSPView, so I added gesture recognizers to LVSTSPViewController.m. When I execute the gesture (e.g., do a long press), the code crashes with message "EXC_BAD_ACCESS (code=1, address=...)" in main.m.
Relevant code:
In LVSAppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
// Get pointer to app bundle
NSBundle *appBundle = [NSBundle mainBundle];
// Get xib file
LVSTSPMasterViewController *tspmvc = [[LVSTSPMasterViewController alloc] initWithNibName:#"LVSTSPMasterViewController" bundle:appBundle];
self.window.rootViewController = tspmvc;
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
In LVSMasterViewController.m:
#interface LVSTSPMasterViewController () <UIScrollViewDelegate>
// IBOutlet declarations
#property (nonatomic, weak) IBOutlet UIScrollView *TSPScrollView;
// Pointers for convenience
#property (strong, nonatomic) LVSTSPView *TSPView;
#end
#implementation LVSTSPMasterViewController
- (void)viewDidLoad
{
// Create LVSTSPViewController
LVSTSPViewController *tspvc = [[LVSTSPViewController alloc] init];
// Set up pointer to LVSTSPView
self.TSPView = (LVSTSPView *)tspvc.view;
// Set frame of LVSTSPView
self.TSPView.frame = self.TSPScrollView.bounds;
// Set tspvc's view as subview of TSPScrollView
[self.TSPScrollView addSubview:tspvc.view];
// Set up scroll view
self.TSPScrollView.pagingEnabled = NO;
self.TSPScrollView.contentSize = self.TSPView.frame.size;
self.TSPScrollView.minimumZoomScale = 1.0;
self.TSPScrollView.maximumZoomScale = 3.0;
// Set scroll view's delegate property
self.TSPScrollView.delegate = self;
}
In LVSTSPViewController.m:
- (void)loadView
{
// Create view
LVSTSPView *view = [[LVSTSPView alloc] initWithFrame:CGRectZero];
self.view = view;
// Long-press recognizer
UILongPressGestureRecognizer *pressRecognizer =
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress:)];
[self.view addGestureRecognizer:pressRecognizer];
}
- (void)longPress:(UIGestureRecognizer *)gr
{
NSLog(#"longPress:");
}
One other note: If I set up the gesture recognizer in viewDidLoad: in LVSTSPMasterViewController.m, like so --
UILongPressGestureRecognizer *pressRecognizer =
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress:)];
[self.TSPView addGestureRecognizer:pressRecognizer];
-- (and of course add longPress: to LVSTSPMasterViewController.m), then it works. But this doesn't seem like the right approach since the LVSTSPMasterViewController is not the view controller for LVSTSPView.
Any help will be appreciated!
Your view controller LVSTSPViewController is not retained anywhere or in other words your master view controller needs to retain it.
In LVSMasterViewController.m, change your code to
#implementation LVSTSPMasterViewController
{
LVSTSPViewController *tspvc;//creates strong reference to self(LVSTSPMasterViewController)
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
tspvc = [[LVSTSPViewController alloc] init];
// Set up pointer to LVSTSPView
self.TSPView = (LVSTSPView *)tspvc.view;
// Set frame of LVSTSPView
self.TSPView.frame = self.TSPScrollView.bounds;
// Set tspvc's view as subview of TSPScrollView
[self.TSPScrollView addSubview:tspvc.view];
// Set up scroll view
self.TSPScrollView.pagingEnabled = NO;
self.TSPScrollView.contentSize = self.TSPView.frame.size;
self.TSPScrollView.minimumZoomScale = 1.0;
self.TSPScrollView.maximumZoomScale = 3.0;
// Set scroll view's delegate property
self.TSPScrollView.delegate = self;
}
Also always call [super viewDidLoad] first in your viewDidLoad method. I would suggest to read more about view and view controller in the apple documentation. You necessarily do not need a view controller with every view. It all depends on your specific case. One view controller can manage multiple views.
I tried to implement stripe into an iOS app through its online documentation. Everything good so far, now pushing the paymentView onto my navigation controller stack I get a completely broken screen. Thought it'd be a problem with the stripe view but when I do not log in (see code below - no identification token given) and the login screen is being pushed instead, it is completely black too. It cant be a problem with that view cause it loads just fine if I push the login view from another view before this one.
So why does pushing view via the buyButtonAction below give me black / fucked up screens?!
Ive been on this for hours.. nothing seems to work.
A pic:
the important code part:
#interface PaymentViewController ()
#end
#implementation PaymentViewController
#synthesize stripeCard = _stripeCard;
#synthesize stripeView;
#synthesize passedProductId;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.stripeView = [[STPView alloc] initWithFrame:CGRectMake(15,20,290,55)
andKey:#"pk_test_45mqixOu8N9S4lQ6cdn1OXBD"];
self.stripeView.delegate = self;
[self.view addSubview:self.stripeView];
}
And the call:
-(void)buyButtonAction:(id)sender
{
tokenClass *tokenObject = [tokenClass getInstance];
NSLog(#"%#", tokenObject.token);
if (tokenObject.token == nil) {
LoginController *loginController = [[LoginController alloc] init];
[self.navigationController pushViewController:loginController animated:YES];
} else {
NSLog(#"%#", tokenObject.token);
CGPoint hitPoint = [sender convertPoint:CGPointZero toView:self.tableView];
NSIndexPath *hitIndex = [self.tableView indexPathForRowAtPoint:hitPoint];
PaymentViewController *payView = [[PaymentViewController alloc] init];
payView.passedProductId = [[self.productData valueForKey:#"id"] objectAtIndex:hitIndex.row];
NSLog(#"passing %#", payView.passedProductId);
// push payment view
payView.navigationItem.title = #"One-Click-Payment";
[self.navigationController pushViewController:payView animated:YES];
}
}
We can see that there's a view behind the navigation bar. It's an iOS 7 related issue. Add this line to your viewDidLoad:
if ([self respondsToSelector:#selector(edgesForExtendedLayout)])
self.edgesForExtendedLayout = UIRectEdgeNone;
Or change your self.stripeView frame by adding 64 to y:
CGRectMake(15,84,290,55)
Useful link: https://stackoverflow.com/a/18103727/1835155
I have been developing in Objective-C for two months, so I am quite new to this language and iOS environment. I am updating to iOS7 an app that is working fine for iOS6.
I am getting the next error when a modal view with a web view inside is presented, only in iOS7 and this is working in iOS6. There is a URL request inside but I cannot find what is causing the error.
'-[__NSMallocBlock__ absoluteURL]: unrecognized selector sent to instance 0x16e8b020'
This is the viewWillAppear method on the modal view controller:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (!self.webView.request) {
//THE NEXT LINE THROWS THE ERROR
NSURLRequest *req = [[NSURLRequest alloc] initWithURL:self.initialURL];
[self.webView loadRequest:req];
}
}
Maybe I am doing something silly but really now I do not know where to look at.
If anyone has experienced something like that before, I will appreciate some help. Thanks in advance.
EDIT:
#interface MyViewController ()
#property (copy, nonatomic) NSURL *initialURL;
#end
#implementation MyViewController
- (id)initWithURL:(NSURL *)initialURL
{
self = [super init];
if (self) {
_initialURL = initialURL;
_webView = [[UIWebView alloc] init];
_webView.backgroundColor = [UIColor clearColor];
_webView.opaque = NO;
_webView.delegate = self;
[self.view addSubview:_webView];
self.modalPresentationStyle = UIModalPresentationFormSheet;
self.view.backgroundColor = [UIColor whiteColor];
}
return self;
}
Method call:
self.modalWebViewController = [[[MyViewController alloc] initWithURL:url] autorelease];
I assume that iOS calls absoluteURL on the self.initialURL object passed to the initWithURL: method. However, the object receiving this message is an NSMallocBlock, so there seems to be something wrong. I assume that your self.initialURL object should be of type NSURL. If so, this would indicate a memory management problem causing the pointer of self.initalURL to point to somewhere else in memory (not to the object you want it to point to).
You could try to run your app with NSZombiesEnabled which prevents any objects from being actually deallocated and instead warns you if a deleted object is still accessed.
You can activate NSZombies in the scheme to run your app (click on the name of your app in Xcode's toolbar on the upper right and choose "Edit Scheme..." from the pop-up menu). In the run-configuration in the "Diagnostics" tab there is a checkbox for activating Zombie objects.
I've lost enough sleep trying to figure this one out. I have a tabbed application that supports all orientations. Everything works completely fine when the app starts in portrait and switches to landscape. When the app starts in landscape however and then switches to portrait, I lose response ONLY on the bottom quarter of the screen! It's as if something in the background is not adjusting the "user interaction" area from landscape bounds to portrait bounds when the orientation changes.
I have two tabs at the moment. When I switch to the second tab, and then back to the first, the problem goes away! I'm able to interact with the bottom quarter of the screen again. I've gotten that similar problem before when I setup the tab view controller away in viewDidLoad as opposed to viewDidAppear. Everything is in viewDidAppear now.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidAppear:(BOOL)animated
{
NSArray* controllers = nil;
TimeTableViewController * timeTableViewController = [[TimeTableViewController alloc] init];
FRLayeredNavigationController *timeTableNavController = [[FRLayeredNavigationController alloc] initWithRootViewController:timeTableViewController configuration:^(FRLayeredNavigationItem *item)
{
item.hasChrome = YES;
}];
timeTableNavController.dropLayersWhenPulledRight = true;
TestViewController* vc2 = [[TestViewController alloc] init];
timeTableNavController.tabBarItem = [[UITabBarItem alloc]
initWithTitle:#"Student Lookup"
image:[UIImage imageNamed:#"search_gray.png"]
tag:0];
timeTableNavController.tabBarItem = [[UITabBarItem alloc]
initWithTitle:NSLocalizedString(#"Attendance", #"Attendance")
image:[UIImage imageNamed:#"attendance_gray.png"]
tag:0];
timeTableNavController.view.backgroundColor = [UIColor lightGrayColor];
controllers = [NSArray arrayWithObjects:timeTableNavController,vc2, nil];
self.viewControllers = controllers;
}
- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return YES;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
I don't think the FRLayeredNavigationController library is an issue. Is there any method I can call that readjusts the "area of response" when switching from landscape to portrait? That would be a life-saver. Someone please beat some sense into me!
In fact when you start your app in landscape mode, it should starts in portrait and then your viewcontroller need to rotate this.
In fact you have 3 approaches:
First and i think the best:
https://stackoverflow.com/a/10533561/2204866
Or you can create separates views with separate xib's for each
mode.
Easiest way to support multiple orientations? How do I load a custom NIB when the application is in Landscape?
And the last use auto layout.
I am new to Core Animation and having trouble implementing a CALayer object with the drawLayer method in a delegate.
I have narrowed the problem down to a very simple test. I have a main viewController named LBViewController that pushes a secondary viewController called Level2ViewController. In the level 2 controller, in viewWillAppear:, I create a CALayer object with it's delegate=self (i.e. the level 2 controller). Whether or not I actually implement the drawLayer:inContext: method I have the same problem -- when I return to the main viewController I get a zombie crash. In the profiler it appears that the object in trouble is the level 2 viewController object -- which is being dealloc'ed after it's popped.
I've tried using a subclassed CALayer object instead of the delegate and it works fine. If I comment out the delegate assignment it also runs fine. I would like to understand why delegation is causing this problem. Any advice is greatly appreciated.
Here's my code ---
Level2ViewController
#implementation Level2ViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CALayer *box1 = [[CALayer alloc] init];
box1.delegate = self; // problem disappears if I comment out this assignment
box1.backgroundColor = [UIColor redColor].CGColor;
box1.frame = CGRectMake(10,10,200,300);
[self.view.layer addSublayer:box1];
[box1 setNeedsDisplay];
}
// makes no difference whether or not this method is defined as long
// as box1.delegate == self
- (void)drawLayer:(CALayer *)theLayer inContext:(CGContextRef)theContext
{
CGContextSaveGState(theContext);
CGContextSetStrokeColorWithColor(theContext, [UIColor blackColor].CGColor);
CGContextSetLineWidth(theContext, 3);
CGContextAddRect(theContext, CGRectMake(5, 5, 40, 40));
CGContextStrokePath(theContext);
CGContextRestoreGState(theContext);
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
The method in LBViewController (the main controller) that pushes the level 2 view controller
- (IBAction)testAction:(id)sender {
Level2ViewController *controller = [[Level2ViewController alloc]
initWithNibName:#"Level2ViewController" bundle:nil];
controller.title = #"Level2";
// this push statement is where the profiler tells me the messaged zombie has been malloc'ed
[self.navigationController pushViewController:controller animated:YES];
[controller release];
}
You may want to set the layer's delegate to nil before the delegate object is released. So in your Leve2ViewController do this:
-(void)viewWillDisappear:(BOOL)animated
{
if (box1) {
box1.delegate = nil;
}
box1 = nil;
}
Obviously this requires, that box1 is turned into a field (so it is accessible in viewWillDisappear:)
Since you create box1in viewWillAppear: the code above uses viewWillDisappear:. Recently, when I ran into a similar problem, I had a separate delegate object in which I used init and dealloc.
Note: You call [super viewDidAppear:animated]; in viewWillAppear. Looks like a typo or copy/paste glitch :-)