iOS code organizing pattern - ios

I'm fairly new to iOS development. I normally create all my UIViews, UILabels, UIButtons etc. in code instead of using the Interface Builder. This makes it very easy for my UIViewControllers to get extremely large and hard to follow, mixing outlet declarations with actual actions and logic.
- (UIButton *) continueButton {
// if button is nil
if(_continueButton == nil) {
UIButton *button = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
/* more configuration */
_continueButton = button;
}
return _continueButton;
}
// occures when continue button is tapped
- (void) buttonTouch:(id) sender
{
UIButton *button = ((UIButton *)sender);
if(button == continueButton) {
/* do stuff */
}
}
What i want to accomplish is to separate the actual actions, animations, logic and so on,
and store the buttons, labels and other views' declaration in a different file.
As a solution I was thinking of creating another UIViewController with the outlets and embedding it as a child inside the main one which holds the logic, using "addChildViewController".
Would this be the right way to go ? How do you handle it?

I think you are going about this the wrong way. Using IB is a smart thing to do in most cases. Xcode will create the code for the UI elements more efficiently then you will.
Also, using IB AND the Assistant Editor will make wiring things up much faster and more efficient. It also ensures that cleanup code is added where needed.
For code separation within my implementation file, I personally use #pragma mark - to separate my Outlet declarations from my Action declarations and then always put the methods for the Actions separate (usually at the top) from the other methods.
I also declare the properties for Outlets privately unless for some reason they need to be public.
My structure:
My methods at the top
Actions next
My delegate methods
Apple's Methods

Related

iOS: showing/hiding UI elements dynamically

I have an app with a UITableView of results in a center column, and a little search bar at the top. I want to dynamically add/remove a button that says "reset search" and pin it to the top of the view.
There are a couple ways to go about it, and I'm worried that they both look ugly or hacky to me. To wit:
Add the button in the storyboard editor, and show/hide it in code. The trouble is I've already got a bunch of views specified this way in the storyboard, and so positioning/selecting them is a huge pain since they overlap each other.
Add the button in code. Except now my UI is specified in two places: the stuff that's in the storyboard, and the additional modifications that take place in the code.
What's the standard way of doing something like this? And how can I prevent my storyboards from becoming a big mess when I've got buttons/dialogs/etc. that need to be dynamically shown/hidden?
Well my first answer is to not use storyboards in the first place. However, I understand that's not helpful in this case.
If I were you, I would do option 2. It's a one off for this single button and it has a specific use case. It doesn't hurt to specify it in code. The following is for the
.h
#property (nonatomic, strong) UIButton *resetButton;
And
.m
//I'm guessing you're using a VC, so I'd put this in viewDidLoad
self.resetButton = [[UIButton alloc]initWithFrame:YOUR FRAME];
self.resetButton.alpha = 0.0;
//any other styling
[self.view addSubview:self.resetButton];
self.resetButton addTarget:self action:#selector(onReset) forControlEvents:UIControlEventTouchUpInside];
//and then add these three methods
- (void)onReset {
//called when reset button is tapped
}
- (void)showResetButton {
[UIView animateWithDuration:.3 animations:^{
self.resetButton.alpha = 1.0;
}];
}
- (void)hideResetButton {
[UIView animateWithDuration:.3 animations:^{
self.resetButton.alpha = 0.0;
}];
}
I don't know if I have understood, but if you want to hide an object with an action, you can do so :
- (IBAction)myaction:(id)sender
{
self.object1.hidden = false ;
self.object2.hidden = true ;
self.object3.hidden = false ;
}
Both ways are perfect, I personally prefer the Storyboard one because it lets you arrange the button more easily and it's easier to add constraints for auto-layout(if needed) in Interface Builder than in code.
For your second question: If your storyboard is cluttered and views are all over the place, I would suggest that you select your views from the side bar, rather thank trying to click on them. Also, if you want to move the selected view, adjust the coordinates in the Utilities panel instead of dragging it with the mouse.

Hide or temporarily remove a child ViewController from a parentViewController?

(asking and self-answering, since I found no hits on Google, but managed to find a solution in the end by trial and error)
With iOS 5 and 6, Apple added some ugly hacks to make InterfaceBuilder support "embedded" viewcontrollers. They didn't document how those work, they only give code-level examples, and they only cover a limited subset of cases.
In particular, I want to have an embedded viewcontroller that is sometimes hidden - but if you try the obvious approach it doesn't work (you get a white rectangle left behind):
childViewController.view.hidden = TRUE;
Why don't you just create an IBOutlet to your container view and do
self.containerView.hidden = YES;
How they've done it appears to be a variation on the manual way that worked since iOS 2 (but which only supported views, not viewcontrollers) - there is a real, genuine UIView embedded into the parent (not mentioned in the source code examples - it's only added when you use InterfaceBuilder!).
So, instead, if you do:
childViewController.view.superview.hidden = TRUE;
...it works!
Also, counterintuitively, you can call this method at any time from viewDidLoad onwards - the "embed segue" hack from Apple is executed before viewDidLoad is called.
So you can do this on startup to have your childViewController start off invisible.
Use This [self.childviewController setHidden:YES];
In case somebody will need to hide/show all child views or iterate over them:
func hideChildrenViews() {
for view in self.view.subviews {
(view as! UIView).hidden = true
}
}
func showChildViews() {
for view in self.view.subviews {
(view as! UIView).hidden = false
}
}

iOS: automatic way to tab between text fields

I have a relatively large iPhone application with many views.
I would like implement a next/previous option on my keyboard.
I have managed to implement it UI-wise, with some code examples i saw online, but all of them are assuming we need to add code to each view controller to implement the actual transition between the text fields.
My question is: is there a general way to know, given some text field, who is the next field in order? (i.e without refactoring each of my view controllers)
I ask this question because when i use the iPhone simulator and press the computer's Tab key - the switch between the fields happen, so i wonder if there is a built-in or generic way to implement it on iOS.
clarification:
is there a way of doing it without adding a specific code for each type of view controller? (adding a generic code is acceptable)
I want to write how i solved this problem, with the help of many good answers given to me here :)
First, i could not create fully generic code that creates tab regardless of the view it is in.
Instead i created this thing, which i think is the most generic solution with the firstResponder method not working:
i created custom toolbar with my next/previous/done buttons and appropriate actions delegate.
than i extended UIViewController by adding category "Tab". this category declares a fieldsArray and implements the delegate method.
Now what every specific view controller needs to do (beside importing the category) is to provide this fieldsArray according to its properties and calling the init method which adds the buttons toolbar to this fields
I hope you could benefit from this, and again thanks for all the good answers
you could have a method in a utility class that takes as arguments a textfield and a viewcontroller. then you could use the "tag"-attribute of the textfields to find the next textfield in that viewcotroller, assuming that you assigned the tags accordingly. numbers would be great, i think. a simple callback method in the vc could handle the focus-change. thats about as generic as i can see right now.
This is some generic code that I came up with:
// add a property for the fieldsArray
//add this in viewDidLoad
_fieldsArray = [[NSMutableArray alloc] init];
NSArray *viewsArray = [self.view subviews];
for (id view in viewsArray) {
if ([view isKindOfClass:NSClassFromString(#"UITextField")]) {
[_fieldsArray addObject:view];
}
}
//add this in your action that switches the fields
for (UITextField *field in _fieldsArray) {
if ([field isFirstResponder]) {
if ([fieldsArray lastObject] == field) {
[_fieldsArray[0] becomeFirstResponder];
}else {
NSUInteger nextIndex = [_fieldsArray indexOfObject:field] + 1;
[_fieldsArray[nextIndex] becomeFirstResponder];
}
break;
}
}
Before using it it should be improved.
1) find all subviews of self.view recursively
2) do some checks if the arrays are empty or nil or have just one object in them.
Good luck!

TableView and PKRevealController gesture conflicts - How to really solve them?

I am trying to implement a view with sliding side menus, such as with PKRevealController in iOS 6.1. A simple demo of this issue with source code on github is here, however you might not need to grab it if you already understand gestureRecognizer delegate implementation.
The problem I see is that two gestures that my users will want to use are going to be mutually confused for each other. The UITableView in the center (main screen) of the application should be able to use the swipe-right gesture to delete, but I still want a swipe that occurs across the top navigation area to result in exposing the side menus.I also intend to show other things than just the Table view, and at runtime I plan to swap out the main view with a different view, whenever a user selects a button on one of the side menus. This is kind of like a "hidden side tray UITabBarController" that I'm going for, but I want the side bars to be revealed only when the main "front view" controller is NOT a UITableView or its subviews.
Right now, using the demo sources that comes with PKRevealController, and adding deletion support to the main view's UITableView, no slide gesture to delete a row is possible. (You have to add one table view method to enable deletion support in the UITable view, which I did add.)
This was asked here, but the answer stated is incomplete, and as seen below, does not work for me and I have no idea why, because it appears that this delegate method is not invoked at any time where I return a YES, and yet it goes ahead anyways and begins a gesture.
Update The answer in the previous question is also wrong, as compared to the WIKI/FAQ answer I placed below.
I have only modified the class PKRevealController.m by adding this:
- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
UIView *view1 = otherGestureRecognizer.view;
UIView *view2;
if (view1) {
view2 = view1.superview;
};
if ([gestureRecognizer.view isKindOfClass:[UITableView class]])
{
return NO;
}
// Co-operate by not stealing gestures from UITableView.
if ([view1 isKindOfClass:[UITableView class]]) {
return NO;
}else if ([view1 isKindOfClass:[UITableViewCell class]]) {
return NO;
// UITableViewCellContentView
}
else if (view2 && [view2 isKindOfClass:[UITableViewCell class]]) {
return NO;
// UITableViewCellContentView
}
else
{
return YES; // NEVER GETS HIT. BREAKPOINT HERE!
}
}
What confuses me is that at no point does the return YES code above get hit (I have a breakpoint on it) and yet, the Gesture controller is still stealing the gesture.
Note: I have made an evil hack, but I thought that I could prevent this cleanly. Here is my evil hack:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer == self.revealPanGestureRecognizer)
{
CGPoint translation = [self.revealPanGestureRecognizer translationInView:self.frontViewContainer];
BOOL begin = (fabs(translation.x) >= fabs(translation.y));
// BEGIN EVIL HACK
if (_topLimitY > 0) {
CGPoint location = [gestureRecognizer locationInView:gestureRecognizer.view];
if (location.y>_topLimitY) // _topLimitY = 55 for instance.
begin = NO;
}
// END EVIL HACK.
return begin;
}
else if (gestureRecognizer == self.revealResetTapGestureRecognizer)
{
return ([self isLeftViewVisible] || [self isRightViewVisible]);
}
return YES;
}
Right now in my evil hacked demo, I have set the topLimitY property (that I added to PKRevealController's properties) to 55, which allows me to swipe on the nav bar area of the front view, but not on the table view which takes up the rest of the demo.
Note that I plan to have multiple main views, and only want to defeat the gesture recognition on the whole main area if the view is a UITableView or some sub-view thereof. That is why I call my hack above a hack. Because I thought you could tell the gesture recognizer to go away and not bother you, and yet it doesn't work, it doesn't even invoke the shouldRecognize method, it just goes ahead and does the next thing in its list of things to do.
I should really read the WIKI first shouldn't I?
This is a FAQ, it says so right here:
When instantiating the controller pass this option in your options dictionary:
NSDictionary *options = #{
PKRevealControllerRecognizesPanningOnFrontViewKey : #NO
};
This will disable pan-based reveal for the entire front view. Now, you can use the revealPanGestureRecognizer and add it to any view you desire to be panned on that doesn't interfere with your table view, to enable gesture based reveal.
I'd advise (if working with a table based environment with swipe'able cells) you, to add the revealPanGestureRecognizer to your front view controller's navigation bar (which it most likely has):
[self.navigationController.navigationBar addGestureRecognizer:self.revealController.revealPanGestureRecognizer];
And voilĂ  - panning doesn't interfere with your table view anymore.
more info at:
https://github.com/pkluz/PKRevealController/issues/76
Thank you Wiki. If only I had read it all first.
The above completely answers my question and was already there on the wiki. I'm answering my own question because it seems Google always comes to Stackoverflow first, and that might help other confused developers in the future.
Update If the above thing blows up when you try it, it's probably being done too early. Here's a slightly more robust version of the above fix:
// Additional gesture recognition linkups. The underscore variables here
// are implementation-section ivars in my app-delegate, that I have already
// checked are valid and initialized, and this is the last thing in my app delegate
// didFinishLaunch... method, before the return YES:
UIGestureRecognizer *rec = _revealController.revealPanGestureRecognizer;
if (rec) {
[_frontViewNavController.navigationBar addGestureRecognizer:rec];
}
Use This :
self.revealController.frontViewController.revealController.recognizesPanningOnFrontView = YES;

How do I programmatically get the state of UIBarButtonItems?

With a UIControl such as a UIButton you can use something like
myControl.state
to figure out whether the control is currently being pressed down.
However, I need to do the same with some UIBarButtonItems (which are not derived from UIControl), so that I can stop my table from editing while one of them is pressed down.
Here's my code:
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
//other checks
for(int b=0; b<self.toolbar.items.count; b++)
{
UIControl *currentControl= [self.toolbar.items objectAtIndex:b];
if(currentControl.state==UIControlStateHighlighted)
{
return NO;
}
}
return YES;
}
Obviously, it doesn't work, since it assumes that UIBarButtonItems can be treated as UIControls, but how would I do what I'm trying to do here?
If you want more control over your UIBarButtonItems the best thing to do is to recreate them as UIButtons (using custom art, etc), and then use the -initWithCustomView of UIBarButtonItem to create button items from actual UIViews.
This will give you full access to the usual button interactions methods: the only downside is you won't get the nice bar button style by default, and you'll have to provide the art for this yourself.
I had a similar problem before. I couldn't fix it, so I moved on. Here is what I did:
Use ToolBar instead of navigation bar, then use UIButton instead of UIBarButtonItem inside the toolbar.

Resources