Using SharedSingleton to call method - ios

I have a UIviewController. I would to like to add as subview a UIView, but since the UIView doesn't have a viewdidload method, I need to manually call a method. I am doing this using shared singleton in this way:
In the view.m i have:
+ (TableLoadGroupMembersViewController *)sharedManager
{
static TableLoadGroupMembersViewController *shaderManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shaderManager = [[TableLoadGroupMembersViewController alloc] init];
});
return shaderManager;
}
- (void)LoadTheView
{
TableViewGroupMembers = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
TableViewGroupMembers.dataSource = self;
TableViewGroupMembers.delegate = self;
[TableViewGroupMembers addSubview:TableViewGroupMembers];
}
in the view.h I have
+ (TableLoadGroupMembersViewController *) sharedManager;
- (void)LoadTheView;
and in the viewcontroller.m I have:
CGRect frame = CGRectMake(100, 100, 300, 100);
TableLoadGroupMembersViewController *view = [[TableLoadGroupMembersViewController alloc] initWithFrame:frame];
[self.view addSubview:view];
TableLoadGroupMembersViewController* sharedSingleton = [TableLoadGroupMembersViewController sharedManager];
[sharedSingleton LoadTheView];
but the app crashes. Why?
EDIT:
The error is: 0x281db5d: pushl %esi ... Thread 1: EXC_BAD_ACCESS

but the app crashes. Why?
So, here you're creating a new instance of your view controller:
TableLoadGroupMembersViewController *view = [[TableLoadGroupMembersViewController alloc] initWithFrame:frame];
This will crash. Views have -initWithFrame methods, but your view controller probably doesn't.
Next, you're adding that view controller as a subview of self.view:
[self.view addSubview:view];
But a view controller is not a view, even if if you name the variable view, so you can't add it as a subview. If the first line didn't crash, this one will.
It looks like you're confusing your view controller class with a UIView subclass. Take a deep breath, read through the code while pretending that someone else wrote it, and try to figure out what's supposed to be happening here.
(It'd be a big help in the future, however, if you took the time to step through your code in the debugger and identify the specific line that's crashing.)
I'm not convinced (at all) that you need a singleton here for any reason, but the crash is almost certainly a matter of sending messages that the receiver doesn't implement, and it comes down to this confusing between view and view controller.

Related

Is it possible to place a Kudan ARCameraViewController inside a UIView

I want to use Kudan inside an existing React-Native App. You can create your own Native Components in React-Native so I thought I could make a Kudan-component.
In React-Native I have to write a function which returns a UIView, RN can place on the screen. But all tutorials about Kudan tell me to make my UIViewController into an ARCameraViewController which I can't do because of all the other RN-Components.
I tried the following (YTARViewController extends ARCameraViewController):
- (UIView *)view
{
UIViewController* controller = [[YTARViewController alloc] init];
UIView* view = [[ARCameraView alloc] init];
controller.view = view;
return view;
}
But this renders my app unresponsive with 100% CPU usage as soon as I instantiate this component from JS, which will run above code and try to place the view on screen, which doesn't happen, because the app is already unresponsive at this point.
Try this:
- (UIView *)view
{
UIViewController* controller = [[YTARViewController alloc] init];
UIView* view = [[ARCameraView alloc] init];
[controller.view addSubview: view];
\\ view.frame = assign frame here.
return view;
}

where to init a UITablewView as a subview

Where should I initialize a UITablewView as a subview, in - (id)initWithFrame:(CGRect)frame, in viewdidload or loadView? Which is the better approach? Where should I make the frame (I mean which is more effective)?
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.backgroundColor = kViewBackgroundColor;
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
_tableView.backgroundView = nil;
_tableView.backgroundColor = [UIColor clearColor];
_tableView.bounces = NO;
[self addSubview:_tableView];
}
return self;
}
On my projects I usually create a baseViewController with a custom initialiser like this:
- (id)init{
self = [self initWithNibName:NSStringFromClass([self class]) bundle:[NSBundle mainBundle]];
if (self) {
//You should create the tableView and other properties here
//and add as subviews inside viewDidLoad
_tableView = [[UITableView alloc]initWithFrame:CGRectMake(0,0,100,100)];
}
return self;
}
and I always create my view controller by using this init method, because I don't think the other vc's need to know the name of the nib file. If I were you I would create subviews inside init method, add as subviews inside viewDidLoad or viewWillAppear and finally releasing them inside dealloc if you are not using ARC.
Generally this depends on your requirement that if you are having custom view called with a tableview as subview then loadview with the initwithframe method will be better and while you want to initialize it from viewcontroller then viewdidload is better. I think you should bifurcate your requirement. Hope this helps.
Since you are initializing table view with zero frame it wont be visible even if you added that
you can try this
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.backgroundColor = kViewBackgroundColor;
UITableView *_tableView = [[UITableView alloc] initWithFrame:self.bounds style:UITableViewStyleGrouped];
_tableView.backgroundView = nil;
_tableView.backgroundColor = [UIColor clearColor];
_tableView.bounces = NO;
[_tableView setAutoresizingMask:UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth]
[self addSubview:_tableView];
}
return self;
}
If you'd like to create subviews programmatically based on superview metrics (frame or bounds), you should do it in viewDidLoad, because if you do in in initializer, metrics of self.view will not be avaliable, and you subviews will be created with zero frame and will not be visible (as Johnykutty) mentioned above.
But that does not seem to be a good practice in case your subview initialization routines require heavy operations.
Offtopic: I've been codin' for a looong time trying to do everything programmatically, never using xibs, etc. Well, if you ask me now, xibs really save your time and make life easier. Consider using interface builder for layout\autolayout, colors\borders etc stuff.

ObjC: ARC releasing dynamically created view controller too early

I have a view controller, named AllThingsViewController that dynamically creates other view controllers, named ThingViewController, and adds their top level view to a UIScrollView. (I'm writing proprietary code so I've changed the names of my classes, but the structure of my code is exactly the same.)
Here's what its loadView method contains:
NSArray *things = [[ThingDataController shared] getThings];
if ([things count] == 0) {
// code in this block is not relevant as it's not being executed...
} else {
for(unsigned int i = 0; i < [things count]; ++i) {
ThingViewController *thingViewController = [[ThingViewController alloc] init];
[thingViewController loadView];
[scrollView addSubview:thingViewController.topView];
thingViewController.topView.frame = CGRectNewOrigin(thingViewController.topView.frame,
0, thingViewController.topView.frame.size.height*i);
[thingViewController displayThing:thing[i]];
}
}
ThingViewController's loadView method looks like this:
- (void)loadView
{
NSArray *topLevelObjs = nil;
topLevelObjs = [[NSBundle mainBundle] loadNibNamed:#"ThingView" owner:self options:nil];
if (topLevelObjs == nil)
{
NSLog(#"Error: Could not load ThingView xib\n");
return;
}
}
When my app starts up everything displays correctly, until I try to tap one of the buttons that exists in the xib being loaded by ThingViewController, at which point it crashes due to an exception: "unrecognized selector sent to instance". It seems that ARC is releasing my ThingViewController instances too early.
Looking at my code, I figured it was because they weren't being held on to anything, so I created an NSMutableArray as an instance variable in my AllThingsViewController class, and started adding the ThingViewControllers to it thusly:
NSArray *things = [[ThingDataController shared] getThings];
if ([things count] == 0) {
// not being executed...
} else {
for(unsigned int i = 0; i < [things count]; ++i) {
ThingViewController *thingViewController = [[ThingViewController alloc] init];
[thingViewController loadView];
[scrollView addSubview:thingViewController.topView];
thingViewController.topView.frame = CGRectNewOrigin(thingViewController.topView.frame,
0, thingViewController.topView.frame.size.height*i);
[thingViewController displayThing:thing[i]];
[allThingsViewControllers addObject:thingViewController];
}
}
However, it didn't change anything, even though those objects are being added to the array. Finally, just to confirm that this is ARC releasing it early, I changed "thingViewController" to be an instance variable in AllThingsViewController and changed:
ThingViewController *thingViewController = [[ThingViewController alloc] init];
to be:
thingViewController = [[ThingViewController alloc] init];
Sure enough, the last item in the scrollable list doesn't crash when I tap its buttons, but the other ones do, because its ThingViewController isn't being deallocated.
I'm still relatively new to ARC, but after a bunch of Googling I have no idea how to fix this. What do I do?
Couple of things.
Problem 1:
This looks like the cause of your bug:
[allBillViewControllers addObject:billViewController];
It should be:
[allBillViewControllers addObject:thingViewController];
Right?
Problem 2
You are not properly adding the view controller to your view hierarchy. It should be this:
[self addChildViewController:childViewController];
[childViewController.view setFrame:targetFrame];
[scrollView addSubview:childViewController.view];
[childViewController didMoveToParentViewController:self];
And similarly when removing a child view controller:
[childViewController willMoveToParentViewController:nil];
[childViewController.view removeFromSuperview];
[childViewController removeFromParentViewController];
Problem 3
Never call loadView explicitly on a view controller. It gets called by UIKit whenever you access the view property of a view controller.
Problem 4
You must add the view of the child view controller to your scroll view, not an arbitrary subview topView in its view hierarchy. Refactor your ThingViewController class to make this simpler for yourself. :-)
Let's look at your code:
for(unsigned int i = 0; i < [things count]; ++i) {
ThingViewController *thingViewController = [[ThingViewController alloc] init];
[thingViewController loadView];
[scrollView addSubview:thingViewController.topView];
thingViewController.topView.frame = CGRectNewOrigin(thingViewController.topView.frame,
0, thingViewController.topView.frame.size.height*i);
[billViewController displayThing:thing[i]];
[allBillViewControllers addObject:billViewController];
}
After each loop of the for loop executes, nothing will have a strong reference to the ThingViewController. Thus, it gets released and destroyed.
If ThingViewController is a subclass of UIViewController, then it should be made a "child view controller" of the scrollview's view controller. I recommend reading the section on from the View Controller Programming Guide on creating custom container view controllers (i.e., a view controller that encapsulates and displays other view controllers).

UITableView headings shown on top of MBProgressHUD

So I have a subclass of UITableViewController that loads some data from the internet and uses MBProgressHUD during the loading process. I use the standard MBProgressHUD initialization.
HUD = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:HUD];
HUD.delegate = self;
HUD.labelText = #"Loading";
[HUD show:YES];
This is the result:
.
Is there any way to resolve this issue, or should I just abandon MBProgressHUD?
Thanks!
My solution was pretty simple. Instead of using self's view, I used self's navigationController's view.
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
This should work for the OP because his picture shows he's using a UINavigationController. If you don't have a UINavigationController, you might add another view on top of your UITableView, and add the HUD to that. You'll have to write a little extra code to hide/show this extra view.
An unfortunate thing with this simple solution (not counting my idea adding another view mentioned above) means the user can't use the navigation controls while the HUD is showing. For my app, it's not a problem. But if you have a long running operation and the user might want to press Cancel, this will not be a good solution.
It's probably because self.view is a UITableView, which may dynamically add/remove subviews including the headers, which could end up on top of the HUD after you add it as a subview. You should either add the HUD directly to the window, or (for a little more work but perhaps a better result) you could implement a UIViewController subclass which has a plain view containing both the table view and the HUD view. That way you could put the HUD completely on top of the table view.
My solution was:
self.appDelegate = (kmAppDelegate *)[[UIApplication sharedApplication] delegate];
.
.
_progressHUD = [[MBProgressHUD alloc] initWithView:self.appDelegate.window];
.
[self.appDelegate.window addSubview:_progressHUD];
Works like a charm for all scenarios involving the UITableViewController. I hope this helps someone else. Happy Programming :)
Create a category on UITableView that will take your MBProgressHUD and bring it to the front, by doing so it will always appear "on top" and let the user use other controls in your app like a back button if the action is taking to long (for example)
#import "UITableView+MBProgressView.h"
#implementation UITableView (MBProgressView)
- (void)didAddSubview:(UIView *)subview{
for (UIView *view in self.subviews){
if([view isKindOfClass:[MBProgressHUD class]]){
[self bringSubviewToFront:view];
break;
}
}
}
#end
A simple fix would be to give the z-index of the HUD view a large value, ensuring it is placed in front of all the other subviews.
Check out this answer for information on how to edit a UIView's z-index: https://stackoverflow.com/a/4631895/1766720.
I've stepped into a similar problem a few minutes ago and was able to solve it after being pointed to the right direction in a different (and IMHO more elegant) way:
Add the following line at the beginning of your UITableViewController subclass implementation:
#synthesize tableView;
Add the following code to the beginning of your init method of your UITableViewController subclass, like initWithNibName:bundle: (the beginning of viewDidLoad might work as well, although I recommend an init method):
if (!tableView &&
[self.view isKindOfClass:[UITableView class]]) {
tableView = (UITableView *)self.view;
}
self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
self.tableView.frame = self.view.bounds;
self.tableView.contentInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0);
[self.view addSubview:self.tableView];
Then you don't need to change your code you posted in your question any more. What the above code does is basically seperating the self.tableView from self.view (which was a reference to the same object as self.tableView before, but now is a UIView containing the table view as one might expect).
I've Just solved that issue manually , it has been 2 years since Chris Ballinger asked but maybe someone get used of what is going on here.
In UITableViewController i execute an HTTP method in viewDidLoad , which is running in background so the table view is loaded while the progress is shown causing that miss.
i added a false flag which is changed to yes in viewDidLoad, And in viewDidAppear something like that can solve that problem.
-(void) viewDidAppear:(BOOL)animated{
if (flag) {
[self requestSomeData];
}
flag = YES;
[super viewDidAppear:animated];
}
I had the same problem and decided to solve this by changing my UITableViewController to a plain UIViewController that has a UITableView as a subview (similar to what jtbandes proposed as an alternative approach in his accepted answer). The advantage of this solution is that the UI of the navigation controller isn't blocked, i.e. users can simply leave the ViewController in case they don't want to waiting any longer for your timely operation to finish.
You need to do the following changes:
Header file:
#interface YourViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
- (id)initWithStyle:(UITableViewStyle)style;
#end
Implementation file:
#interface YourViewController ()
#property (nonatomic, retain) UITableView *tableView;
#property (nonatomic, retain) MBProgressHUD *hud;
#end
#implementation YourViewController
#pragma mark -
#pragma mark Initialization & Memory Management
- (id)initWithStyle:(UITableViewStyle)style;
{
self = [super init];
if (self) {
// create and configure the table view
_tableView = [[UITableView alloc] initWithFrame:CGRectNull style:style];
_tableView.delegate = self;
_tableView.dataSource = self;
}
return self;
}
- (void)dealloc
{
self.tableView = nil;
self.hud = nil;
[super dealloc];
}
#pragma mark -
#pragma mark View lifecycle
- (void)loadView {
CGRect frame = [self boundsFittingAvailableScreenSpace];
self.view = [[[UIView alloc] initWithFrame:frame] autorelease];
// add UI elements
self.tableView.frame = self.view.bounds;
[self.view addSubview:self.tableView];
}
- (void)viewWillDisappear:(BOOL)animated
{
// optionally
[self cancelWhateverYouWereWaitingFor];
[self.hud hide:animated];
}
The method -(CGRect)boundsFittingAvailableScreenSpace is part of my UIViewController+FittingBounds category. You can find its implementation here: https://gist.github.com/Tafkadasoh/5206130.
In .h
#import "AppDelegate.h"
#interface ViewController : UITableViewController
{
MBProgressHUD *progressHUD;
ASAppDelegate *appDelegate;
}
In .m
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
appDelegate = (ASAppDelegate *) [UIApplication sharedApplication].delegate;
progressHUD = [MBProgressHUD showHUDAddedTo:appDelegate.window animated:YES];
progressHUD.labelText = #"Syncing To Sever";
[appDelegate.window addSubview:progressHUD];
This should work.
[MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
And to remove you can try
[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];

UIView and UIViewController

I know this is really basic stuff but i need to understand whether my understanding of this is correct.
So what i want to do is this. I want an view with a label on which when double tapped flips and loads another view. On the second view i want a UIPickerView and above i have a button saying back. Both views will be of same size as an UIPickerView which is 320px x 216px.
What i am thinking of to do is create two UIViewclasses named labelView and pickerView. I would then create a viewController which on loadView loads labelView then when user double taps the labelView i get an event in labelView class which is sent to my viewController that then can unload loadView and load the pickerView.
Does this sound as the best way to do this ? Is there a simpler way ? I am also unsure how i route the event from the labelView class to the viewControllerclass.
I dont exactly know the most efficient way to do it(as i am also now to this language),but it is for sure that i have solved ur problem. I made a simple program for that.Three classes involved here in my eg are BaseViewController (which will show two views),LabelView and PickerView (according to ur requirement).
In LabelView.h
#protocol LabelViewDelegate
-(void)didTapTwiceLabelView;
#end
#interface LabelView : UIView {
id <LabelViewDelegate> delegate;
}
#property(nonatomic,retain)id <LabelViewDelegate> delegate;
-(void)didTouch;
#end
In LabelView.m
#synthesize delegate;
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self)
{
UILabel* labl = [[UILabel alloc] initWithFrame:CGRectMake(10, 5, frame.size.width-20,20)];
labl.text = #"Some Text";
[self addSubview:labl];
[labl release]; labl = nil;
self.backgroundColor = [UIColor grayColor];
UITapGestureRecognizer* ges = [[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTouch)] autorelease];
ges.numberOfTapsRequired = 2;
[self addGestureRecognizer:ges];
}
return self;
}
-(void)didTouch
{
[delegate didTapTwiceLabelView];
}
//=============================================================
In Pickerview.h
#protocol PickerViewDelegate
-(void)didTapBackButton;
#end
#interface PickerView : UIView <UIPickerViewDelegate,UIPickerViewDataSource>{
id <PickerViewDelegate> delegate;
}
#property(nonatomic,retain)id <PickerViewDelegate> delegate;
#end
In Pickerview.m
#implementation PickerView
#synthesize delegate;
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self)
{
UIPickerView* picker = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 30, 320, 216)];
picker.delegate = self;
picker.dataSource = self;
[self addSubview:picker];
[picker release]; picker = nil;
self.frame = CGRectMake(frame.origin.x, frame.origin.y, 320, 250);
UIButton* btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(10, 1, 50, 27)];
[btn setTitle:#"Back" forState:UIControlStateNormal];
[btn addTarget:self action:#selector(backButton) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:btn];
}
return self;
}
-(void)backButton
{
[delegate didTapBackButton];
}
//====================================================================
in BaseViewController.h
#import "LabelView.h"
#import "PickerView.h"
#interface VarticalLabel : UIViewController<UITextFieldDelegate,PickerViewDelegate,LabelViewDelegate> {
PickerView* myPickerView;
LabelView* myLabelView;
}
#end
In BaseViewController.m
-(void)viewDidLoad
{
[super viewDidLoad];
myPickerView= [[PickerView alloc] initWithFrame:CGRectMake(0, 50, 320, 250)];
[self.view addSubview:myPickerView];
myPickerView.delegate = self;
myLabelView= [[LabelView alloc] initWithFrame:CGRectMake(0, 50, 320, 250)];
[self.view addSubview:myLabelView];
myLabelView.delegate = self;
myPickerView.hidden = YES;
}
#pragma mark PickerViewDelgate
-(void)didTapBackButton
{
myPickerView.hidden = YES;
myLabelView.hidden = NO;
}
#pragma mark LabelViewDelegate
-(void)didTapTwiceLabelView
{
myPickerView.hidden = NO;
myLabelView.hidden = YES;
}
To get events from a button to the view controller, just hook up the button's event, e.g. touch up inside, to a method in the view controller, using interface builder. (Double tapping is probably more complicated though.)
When you say 'flips', do you mean it actually shows an animation of flipping over a view to show a 'reverse' side? Like in the weather app when you hit the 'i' button? I'm assuming this is what you mean.
Perhaps check TheElements sample example on the iPhone Reference Library, it has an example of flip animation.
Btw, it's not strictly necessary to unload the loadView that is being 'hidden' when you flip -- it saves you having to construct it again when you flip back -- but it may be pertinent if you have memory use concerns, and/or the system warns you about memory being low.
Also, what do you mean by "create a UIView"? Do you mean subclass UIView, or just instantiate a UIVIew and add children view objects to it? The latter is the usual strategy. Don't subclass UIView just because you want to add some things to a UIView.
If you've got one screen of information that gives way to another screen of information, you'd normally make them separate view controllers. So in your case you'd have one view controller with the label and upon receiving the input you want, you'd switch to the view controller composed of the UIPickerView and the button.
Supposing you use Interface Builder, you would probably have a top level XIB (which the normal project templates will have provided) that defines the app delegate and contains a reference to the initial view controller in a separate XIB (also supplied). In the separate XIB you'd probably want to add another view controller by reference (so, put it in, give it the class name but indicate that its description is contained in another file) and in that view controller put in the picker view and the button.
The point of loadView, as separate from the normal class init, is to facilitate naming and linking to an instance in one XIB while having the layout defined in another. View controllers are alloced and inited when something that has a reference to them is alloced and inited. But the view is only loaded when it is going to be presented, and may be unloaded and reloaded while the app is running (though not while it is showing). Generally speaking, views will be loaded when needed and unnecessary views will be unloaded upon a low memory warning. That's all automatic, even if you don't put anything in the XIBs and just create a view programmatically within loadView or as a result of viewDidLoad.
I've made that all sound more complicated than your solution, but it's actually simpler because of the amount you can do in Interface Builder, once you're past the curve of learning it. It may actually be worth jumping straight to the Xcode 4 beta, as it shakes things up quite a lot in this area and sites have reported that a gold master was seeded at one point, so is likely to become the official thing very soon.
With respect to catching the double tap, the easiest thing is a UITapGestureRecognizer (see here). You'd do something like:
// create a tap gesture recogniser, tell it to send events to this instance
// of this class, and to send them via the 'handleGesture:' message, which
// we'll implement below...
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(handleGesture:)];
// we want double taps
tapGestureRecognizer.numberOfTapsRequired = 2;
// attach the gesture recogniser to the view we want to catch taps on
[labelView addGestureRecognizer:tapGestureRecognizer];
// we have an owning reference to the recogniser but have now given it to
// the label. We don't intend to talk to it again without being prompted,
// so should relinquish ownership
[tapGestureRecognizer release];
/* ... elsewhere ... */
// the method we've nominated to receive gesture events
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer
{
// could check 'gestureRecognizer' against tapGestureRecognizer above if
// we set the same message for multiple recognisers
// just make sure we're getting this because the gesture occurred
if(gestureRecognizer.state == UIGestureRecognizerStateRecognized)
{
// do something to present the other view
}
}
Gesture recognisers are available as of iOS 3.2 (which was for iPad only; so iOS 4.0 on iPhone and iPod Touch).

Resources