I have a UITabbBarController with a UITableView. Under certain circumstances the TabBarControllers dataset requires updating when a user arrives from another view,
e.g. the initial load when the TabBarController is called the first time, or when the settings are changed.
This dataset update takes about 2 seconds and I want to show an UIActivityIndicatorView.
Trouble is that when I enter from another view I don't know which view to attach it to, since the loading of the tabbarController is carried out in the viewWillAppear method.
Any clues how I can go about this?
I've done this sort of thing in the viewDidAppear method. My code kicks off a background task to load the data from a url. It also hands the background task a selector of a method to call on the controller when it is done. That way the controller is notified that the data has been downloaded and can refresh.
I don't know if this is the best way to do this, but so far it's working fine for me :-)
To give some more details, in addition to the selector of the method to call when the background task has loaded the data, I also and it a selector of a method on the controller which does the loading. That way the background task manages whats going on, but the view controller provides the data specific code.
Here's there viewDidAppear code:
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (reloadData) {
BackgroundTask *task = [[BackgroundTask alloc] initWithMethod:#selector(loadData) onObject:self];
task.superView = self.view.superview;
task.notifyWhenFinishedMethod = #selector(loadFinished);
[task start];
[task release];
}
}
The background task has an optional superView because it will add a new UIView to it containing an activity indicator.
BackgroundTask.m looks like this:
#implementation BackgroundTask
#synthesize superView;
#synthesize longRunningMethod;
#synthesize notifyWhenFinishedMethod;
#synthesize obj;
- (BackgroundTask *) initWithMethod:(SEL)aLongRunningMethod onObject:(id)aObj {
self = [super init];
if (self != nil) {
self.longRunningMethod = aLongRunningMethod;
self.obj = aObj;
}
return self;
}
- (void) start {
// Fire into the background.
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:#selector(execute:)object:nil];
thread.name = #"BackgroundTask thread";
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(taskFinished:) name:NSThreadWillExitNotification object:thread];
[thread start];
[thread release];
}
- (void) execute:(id)anObject {
// New thread = new pool.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (self.superView != nil) {
busyIndicatorView = [[BusyIndicator alloc] initWithSuperview:self.superView];
[busyIndicatorView performSelectorOnMainThread:#selector(addToSuperView)withObject:nil waitUntilDone:YES];
}
// Do the work on this thread.
[self.obj performSelector:self.longRunningMethod];
if (self.superView != nil) {
[busyIndicatorView performSelectorOnMainThread:#selector(removeFromSuperView)withObject:nil waitUntilDone:YES];
}
[pool release];
}
- (void) taskFinished:(NSNotification *)notification {
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSThreadWillExitNotification object:notification.object];
[self performSelectorOnMainThread:#selector(notifyObject)withObject:nil waitUntilDone:NO];
}
- (void) notifyObject {
// Tell the main thread we are done.
if (self.notifyWhenFinishedMethod != nil) {
[self.obj performSelectorOnMainThread:self.notifyWhenFinishedMethod withObject:nil waitUntilDone:NO];
}
}
- (void) dealloc {
self.notifyWhenFinishedMethod = nil;
self.superView = nil;
self.longRunningMethod = nil;
self.obj = nil;
[super dealloc];
}
#end
Finally as I said I put up a activity indicator. I have a xib which contains a 50% transparent blue background with an activity indicator in the middle. There is a controller for it which has this code:
#implementation BusyIndicator
#synthesize superView;
#synthesize busy;
- (BusyIndicator *) initWithSuperview:(UIView *)aSuperView {
self = [super initWithNibName:#"BusyIndicator" bundle:nil];
if (self != nil) {
self.superView = aSuperView;
}
return self;
}
- (void) addToSuperView {
// Adjust view size to match the superview.
[self.superView addSubview:self.view];
self.view.frame = CGRectMake(0,0, self.superView.frame.size.width, self.superView.frame.size.height);
//Set position of the indicator to the middle of the screen.
int top = (int)(self.view.frame.size.height - self.busy.frame.size.height) / 2;
self.busy.frame = CGRectMake(self.busy.frame.origin.x, top, self.busy.frame.size.width, self.busy.frame.size.height);
[self.busy startAnimating];
}
- (void) removeFromSuperView {
[self.busy stopAnimating];
[self.view removeFromSuperview];
}
- (void) dealloc {
self.superView = nil;
[super dealloc];
}
#end
Hoep this helps.
Related
This question is very similar to an existing question asked here UIImagePickerControllerCameraDeviceFront only works every other time I tried the solution presented but it didn't work for me
I have a simplest of a project with two view controllers. In the blue one I am displaying a small UIView with a UIImagePickerController in it. NOTE: I am displaying front facing camera when app is launched.
I hit the next button and go to orange view controller and when I hit the back button and come back to blue view controller the UIImagePickerController flips from Front to rear. I guess the reason is that it thinks its busy and moves to the rear cam. If I keep moving back and forth between the view controllers the camera keeps flipping front, back, front, back, front, back...
Here is my code and screenshots, what am I doing wrong?
In my *.h
#import <UIKit/UIKit.h>
#interface v1ViewController : UIViewController <UIImagePickerControllerDelegate>
{
UIImagePickerController *picpicker;
UIView *controllerView;
}
#property (nonatomic, retain) UIImagePickerController *picpicker;
#property (nonatomic, retain) UIView *controllerView;
#end
In my *.m file (This code is only used when blue colored view controller is displayed)
#import "v1ViewController.h"
#import <MobileCoreServices/UTCoreTypes.h>
#implementation v1ViewController
#synthesize picpicker;
#synthesize controllerView;
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
picpicker = [[UIImagePickerController alloc] init];
picpicker.delegate = self;
picpicker.mediaTypes = [NSArray arrayWithObjects:(NSString *)kUTTypeImage, nil];
picpicker.sourceType = UIImagePickerControllerSourceTypeCamera;
picpicker.cameraDevice = UIImagePickerControllerCameraDeviceFront;
picpicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
picpicker.showsCameraControls = NO;
picpicker.navigationBarHidden = NO;
picpicker.wantsFullScreenLayout = NO;
controllerView = picpicker.view;
[controllerView setFrame:CGRectMake(35, 31, 250, 250)];
controllerView.alpha = 0.0;
controllerView.transform = CGAffineTransformMakeScale(1.0, 1.0);
[self.view addSubview:controllerView];
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionCurveLinear
animations:^{
controllerView.alpha = 1.0;
}
completion:nil
];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[picpicker dismissModalViewControllerAnimated:YES];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[picpicker dismissModalViewControllerAnimated:YES];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
} else {
return YES;
}
}
#end
You are dismissing the controller in both the viewDidDisappear and viewWillDisappear methods.
That could be the cause of your problem.
Although I do not have a device with a camera available right now to verify this, it seems that you're not dismissing the pickerview controller correctly. The documentation states that you should call dismissModalViewControllerAnimated: on the parent controller in order to dismiss the picker (though, calls to presented controllers will propagate to presenters - so this is not the problem), but in your case you're not displaying the controller modally in the first place so it will not work.
What I would try in this case is to release the picker instead (if not under ARC) and set it to nil (instead of calling [picpicker dismissModalViewControllerAnimated:YES];).
PS. In fact, it seems that there is a bigger problem with your design. Since each button is set to present the other party modally you are not dismissing any of the controllers ever. The controllers just keep stacking on each other. You should either consider to embed them in a navigation controller and have it handle the hierarchy or just set dismissModalViewControllerAnimated: (dismissViewControllerAnimated:completion: on iOS5+) as the action of the second controller's button instead of a modal segue.
This is a very simple issue. I don't know why this happens exactly, but it seems that UIImagePickerController was designed to recreated each time it's needed instead of keeping any reference to it, which seems logical if you think about it. Basically, you need to recreate and reconfigure your picker each time. Below I've pasted some code to give an image of what I mean.
Simple solution:
- (UIImagePickerController *)loadImagePicker {
UIImagePickerController *picpicker = [[UIImagePickerController alloc] init];
picpicker.delegate = self;
picpicker.mediaTypes = [NSArray arrayWithObjects:(NSString *)kUTTypeImage, nil];
picpicker.sourceType = UIImagePickerControllerSourceTypeCamera;
picpicker.cameraDevice = UIImagePickerControllerCameraDeviceFront;
picpicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
picpicker.showsCameraControls = NO;
picpicker.navigationBarHidden = NO;
picpicker.wantsFullScreenLayout = NO;
return picpicker;
}
and in:
-(void)viewWillAppear:(BOOL)animated{
if(!self.picpicker){
self.picpicker = [self loadImagePicker];
[self.view addSubview: self.picpicker];
}
}
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.picpicker removeFromSuperview];
self.picpicker = nil;
}
A little background. I have taken GCCalendar which only works in portrait orientation and extended it to work in landscape similar to how the iPhone's Calendar app works. I did this by duplicating the main view class and modifying it to work in landscape. So when the phone is in portrait orientation the CGCalendar is instantiated from the apps main view controller using the original portrait view class and when in landscape orientation using the new modified landscape view class. Most of the other classes in GCCalendar are shared without modification. A few had to be duplicated as well.
I got it all working great except for an issue with the datasource. The datasource is called when the calendar is first loaded and each time the user changes the dates being viewed. Problem is I can't get the datasource call to work on the first call.
I am stumped as it works fine in portrait orientation and I cannot find any difference between the 2 versions.
Following is some of the code that shows how it gets to the datasource call the first time. Subsequent calls removes all the calendar subviews and instantiates them again with the new dates. The duplicated landscape class names end in LS. Otherwise as you can see they are identical.
Does anyone has any idea of where else I might look to resolve this issue?
Thanks,
John
--------------------------------
//App main view controller
- (void)showLandscapeCalendar {
GCCalendarLandscapeView *calendar = [[[GCCalendarLandscapeView alloc] init] autorelease];
calendar.dataSource = self;
calendar.delegate = self;
navigationController = [[UINavigationController alloc] initWithRootViewController:calendar];
[self presentModalViewController:navigationController animated:YES];
}
- (void)showPortraitCalendar {
GCCalendarPortraitView *calendar = [[[GCCalendarPortraitView alloc] init] autorelease];
calendar.dataSource = self;
calendar.delegate = self;
navigationController = [[UINavigationController alloc] initWithRootViewController:calendar];
[self presentModalViewController:navigationController animated:YES];
}
- (NSArray *)calendarEventsForDate:(NSDate *)date{
//build and return the events array
//this is the protocol datasource method
//It is supposed to run every time the date changes in the calendar
}
-------------------------------
// GCCalendarLandscapeView...
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (viewDirty) {
[self reloadDayAnimated:NO context:NULL];
viewDirty = NO;
}
viewVisible = YES;
}
- (void)reloadDayAnimated:(BOOL)animated context:(void *)context {
GCCalendarDayViewLS *nextDayView = [[GCCalendarDayViewLS alloc] initWithCalendarView:self];
}
-------------------------------
//GCCalendarDayViewLS
- (id)initWithCalendarView:(GCCalendarView *)view {
if (self = [super init]) {
dataSource = view.dataSource;
}
return self;
}
- (void)reloadData {
//** first time through the dataSource method does not run
events = [dataSource calendarEventsForDate:date];
}
-------------------------------
// GCCalendarPortraitView...
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (viewDirty) {
[self reloadDayAnimated:NO context:NULL];
viewDirty = NO;
}
viewVisible = YES;
}
- (void)reloadDayAnimated:(BOOL)animated context:(void *)context {
GCCalendarDayView *nextDayView = [[GCCalendarDayView alloc] initWithCalendarView:self];
}
-------------------------------
//GCCalendarDayView
- (id)initWithCalendarView:(GCCalendarView *)view {
if (self = [super init]) {
dataSource = view.dataSource;
}
return self;
}
- (void)reloadData {
**//this one works every time
events = [dataSource calendarEventsForDate:date];
}
I have a UINavigationController application with a root view controller and each time I am pushing a view controller to the stack. Let's say the stack is A B C D where A is the root view controller here. The issue is that when I am at view controller D and do a popToRootViewController it went back to A but A has a back button on it. When I click on back the back just slides in and disappear, why is this happening?
EDIT: I am actually subclassing my UINavigationController so that I can set my rootViewController as follows:
#import "CustomNavigationController.h"
#implementation CustomNavigationController
#synthesize fakeRootViewController;
//override to remove fake root controller
-(NSArray *)viewControllers {
NSArray *viewControllers = [super viewControllers];
if (viewControllers != nil && viewControllers.count > 0) {
NSMutableArray *array = [NSMutableArray arrayWithArray:viewControllers];
[array removeObjectAtIndex:0];
return array; }
return viewControllers; }
//override so it pops to the perceived root
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated {
//we use index 0 because we overrided “viewControllers”
((UIViewController *)[self.viewControllers objectAtIndex:0]).navigationItem.hidesBackButton = YES;
return [self popToViewController:[self.viewControllers objectAtIndex:0] animated:animated]; }
//this is the new method that lets you set the perceived root, the previous one will be popped (released)
-(void)setRootViewController:(UIViewController *)rootViewController {
rootViewController.navigationItem.hidesBackButton = YES;
[self popToViewController:fakeRootViewController animated:NO];
[self pushViewController:rootViewController animated:NO]; }
- (void)dealloc {
self.fakeRootViewController = nil;
[super dealloc]; }
-(id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if(self){
UIViewController *fakeController = [[[UIViewController alloc] init] autorelease];
self.fakeRootViewController = fakeController;
NSMutableArray *array = [NSMutableArray arrayWithArray:[super viewControllers]];
[array insertObject:fakeController atIndex:0];
self.viewControllers = array;
}
return self; }
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use. }
#pragma mark - View lifecycle
/* // Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView { }
*/
/* // Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad]; }
*/
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil; }
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait); }
#end
MORE UPDATE:
So after I set my rootViewController and then push a viewController and then tried to call popToRootViewController from that view, it all worked fine. However, if I push another viewController after the second one and then call the popToRootViewController, now I can see that weird back button on the root.
I too face the same problem. So in your root controller assign leftBarButtonItem equal to nil.
self.navigationItem.leftBarButtonItem = nil;
If your root controller reusing in the program. Then you need to check ->
BOOL needBackBarButton = (1 < [self.navigationController.viewControllers count]) ? YES : NO ;
if (! needBackBarButton)
{
self.navigationItem.leftBarButtonItem = nil;
}
For other controllers
if (needBackBarButton)
{
// Create custom navigationItem here.
}
else
{
self.navigationItem.leftBarButtonItem = nil;
}
I have create a few static UILabel text in Interface Builder, but when running the application. the label is not shown in the application itself, i do not know why.
all other stuff like textfield, buttons except the label is not working
can anybody advise me what had gone wrong ?
This is in IB
this is in App
#import "SettingsViewController.h"
#implementation SettingsViewController
#synthesize drinkLimitText;
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
appDelegate = (DrinkTabsAndNavAppDelegate *)[[UIApplication sharedApplication] delegate];
[super viewDidLoad];
NSLog(#"subView:%d",[self.view.subviews count]);
}
- (void) viewWillAppear:(BOOL)animated {
if ([appDelegate.drinkLimit floatValue] >= 0)
drinkLimitText.text = [NSString stringWithFormat:#"%.0f", [appDelegate.drinkLimit floatValue]];
else
drinkLimitText.text = #"0";
[super viewWillAppear:animated];
}
- (IBAction)textFieldDoneEditing:(id)sender{
NSDecimalNumber *tempValue = [[NSDecimalNumber alloc] initWithString:drinkLimitText.text];
if (tempValue == [NSDecimalNumber notANumber] || [tempValue doubleValue] < 0) {
NSString *msg = [[NSString alloc] initWithFormat: #"Make sure to enter a positive number."];
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Hang on..."
message:msg
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alert show];
[alert release];
[msg release];
} else {
[sender resignFirstResponder];
[self updateDrinkLimit];
}
[tempValue 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 {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)updateDrinkLimit {
NSDecimalNumber *newLimit = [[NSDecimalNumber alloc] initWithString:[drinkLimitText text]];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([newLimit floatValue] >= 0) {
[defaults setFloat:[newLimit floatValue] forKey:kDrinkLimitKey];
appDelegate.drinkLimit = newLimit;
} else {
[defaults setFloat:0 forKey:kDrinkLimitKey];
appDelegate.drinkLimit = 0;
}
[newLimit release];
}
- (IBAction)openOntrackWebsite {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"http://www.ontrack.org.au/"]];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[drinkLimitText release];
[super dealloc];
}
#end
Clean your project and build again.
This is one of the most common problem with Xcode.
My problem was not setting constraints properly. Make sure to set the width and height constraints and add any missing constraints by clicking on that triangle constraint button.
After fixing constraints, they show up.
Make sure you have saved your project.
Also check whether you have done addSubView ion your code.
Clean and then run...
This will solve your issue
hope it helps....
Your hierarchy in interface builder should be as shown below:
UIView
--- Label
--- TextField
--- Label
--- UIButton
I suspect it is :
UIView
--- Label
--- UIButton
Label
Label
Is this xib example from your MainWindow or your ViewController for the tab bar item? Also, did you remember to change the NIB Name in the View Controller properties to the name, usually called SettingsViewController (the name of the xib file), which is what is usually generated if you used Xcode to create the class? In your screen shot, it says "View from 'Settings'", which may not be correct.
I am having a hell of a job trying to get a delegate setup on a custom view.
This is the init code:
- (id)initWithPageNumber:(int)page {
self = [super init];
if (self) {
UpgradeContentView *v = [[UpgradeContentView alloc] initWithPageNumber:page];
[self setView:v];
[v release];
}
return self;
}
then in viewDidLoad I have this:
- (void)viewDidLoad {
[super viewDidLoad];
[(UpgradeContentView *)self.view setDelegate:self];
}
which does not error and the program runs, but none of my delegate methods get called, I have placed NSLog calls and break points but the delegate seems not to be working.
I am doing this completely wrong?
Thanks