subclassing UINavigationController and adding a sliding menu - ios

My goal: To have a sliding menu that will push the current view of my app when it opens.
I've subclassed my UINavigationController in order to create custom "back" buttons and the sliding from the right menu. To do so, inside my subclassed UINavigationController i'm changing the x origin of the current UIViewController (self.view.fram) to a value that will move it to the left x = -100; and by doing so i'm exposing the right menu.
After completing the above I've found out that by moving self.view I'm also moving the touchable area so the button on the right menu won't be accesible \ clickable.
I've read a lot about pointInside and hitTest that can help me achieve what i want but still couldn't implement it in a way that will fit my needs.
I'm aware that there are a 1000 open source projects on github that do exactly what I want but 'd like to write it my self and understand it better.
Thanks.
NavControllerSubCls.h:
#interface NavControllerSubCls : UINavigationController <UINavigationControllerDelegate>
#property (nonatomic, strong) UIView *testView;
#property (nonatomic, strong) UIButton *rightMenuBtn;
#end
NavControllerSubCls.m:
-(void)viewDidLoad
{
[super viewDidLoad];
UIView *navBar = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 41)];
navBar.backgroundColor = [UIColor blueColor];
UIImageView *bkrNavBar = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 320, 39)];
[navBar addSubview:bkrNavBar];
_rightMenuBtn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[_rightMenuBtn setFrame:CGRectMake(240, 20, 52, 19)];
[_rightMenuBtn setTitle:#"Click" forState:UIControlStateNormal];
[_rightMenuBtn addTarget:self action:#selector(showRightMenu) forControlEvents:UIControlEventTouchUpInside];
[navBar addSubview:_rightMenuBtn];
_testView = [[UIView alloc]initWithFrame:CGRectMake(300, 20, 120, 100)];
_testView.backgroundColor = [UIColor greenColor];
UIButton *testBtn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
testBtn.frame = CGRectMake(0, 0, 120, 100);
[testBtn addTarget:self action:#selector(doSomething) forControlEvents:UIControlEventTouchUpInside];
[_testView addSubview:testBtn];
[self.view addSubview:_testView];
[self.view addSubview:navBar];
[self.view insertSubview:_testView aboveSubview:self.view];
}
-(void)doSomething
{
NSLog(#"I'm Working!");
}
- (void)showRightMenu
{
CGRect frame = self.view.frame;
frame.origin.x = -100;
self.view.frame = frame;
}

You mean like the Facebook app? I think a better design would be to have a parent view controller that manages the sliding and two child view controllers, one for the menu and one for the content.
EDIT
The SlidingViewController has two child view controllers: a ContentViewController that takes up all the space and a MenuViewController underneath the ContentViewController.
The SlidingViewController only manages the sliding interaction, whether that's coming from a pan gesture or from a method call. When it's time to show the menu, the SlidingViewController will shift the ContentViewController so that the MenuViewController is visible (by changing the transform or frame property).
The ContentViewController and MenuViewController are just containers for the real view controllers. You use these containers so that you can do whatever you want to them (say if you want to do some really funky animation) without affecting the real view controllers.
So the view controller hierarchy would look like this:
SlidingViewController
/ \
MenuViewController ContentViewController
| |
UITableViewController, etc. UINavigationController, etc.

Related

Is there a way to call the subview of a view controller's self.view in a function outside of viewDidLoad?

Essentially what I'm trying to do here is have a function which sets the alpha of a subview of self.view to 1 (it is hidden by default) which gets called upon pressing a button. i've run into this problem a few times because i declare subviews in viewDidLoad and i don't know of a way to interact with those in a different function (i've tried self.view.subviews[n] but xcode is not happy with that). the only other way i can think of to make this happen is to set self.view equal to the view i want, or maybe even create the view within the function i'm hoping to call with the button (the issue i see with that is potentially the view will not overlay the button, which i want to happen).
here's what i have so far:
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIView *firstBackground = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIView *blackOverlay = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
blackOverlay.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.0];
firstBackground.backgroundColor = [UIColor whiteColor];
UIButton *fakeNotifButton = [UIButton buttonWithType:UIButtonTypeCustom];
[fakeNotifButton addTarget:blackOverlay action:#selector(setupFakeNotifScreen:) forControlEvents:UIControlEventTouchUpInside];
//i haven't finished the button yet, that i don't need any help with
}
-(IBAction)setupFakeNotifScreen:(id)sender {
[UIView animateWithDuration:1.0
delay:0.5
options:UIViewAnimationOptionCurveEaseInOut
animations:^{view = 1.0;} //here is where i want to call blackOverlay
completion:^(BOOL finished){}];
}
#end
edit: i figured it out... didn't even need the blackOverlay view. here's the solution for any future google people... there are probably more efficient ways to do this so i would appreciate anybody who's better than me weighing in:
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *fakeNotifButton = [UIButton buttonWithType:UIButtonTypeCustom];
[fakeNotifButton addTarget:self action:#selector(setupFakeNotifScreen:) forControlEvents:UIControlEventTouchUpInside];
fakeNotifButton.frame = CGRectMake(([UIScreen mainScreen].bounds.size.width/2) - 80, 300, 160, 40);
[fakeNotifButton setTitle:#"test" forState:UIControlStateNormal];
[fakeNotifButton setBackgroundColor:[UIColor systemGreenColor]];
fakeNotifButton.layer.cornerCurve = #"continuous";
fakeNotifButton.layer.cornerRadius = 15;
[self.view addSubview:fakeNotifButton];
}
-(void)setupFakeNotifScreen:(UIButton *)sender {
[UIView animateWithDuration:0.2
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{self.view.backgroundColor = [UIColor blackColor]; sender.alpha = 0.0;}
completion:^(BOOL finished){}];
}
#end
When you declare
UIView *blackOverlay = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
...that reference is inside the method viewDidLoad, so its existence is confined to that method. It is visible only inside the method, and indeed the reference goes out of existence when the method ends. (The view itself also goes out of existence; you have not put it into the interface.)
If you want a persistent reference to this view, you need to declare a property (or at least an instance variable, aka iva), and set it to this view. You still won't be able to do anything visible with the view if you don't actually put it into the interface.

Adding a UISegmentedControl under NavigationBar

I’m adding a UISegmentedControl right under the NavigationBar in a UITableViewController. This is the code.
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationBar = self.navigationController.navigationBar;
UIView *segmentView=[[UIView alloc] initWithFrame:CGRectMake(0, self.navigationBar.frame.size.height, self.navigationBar.frame.size.width, 50)];
[segmentView setBackgroundColor:[UIColor whiteColor]];
segmentView.alpha = 0.95;
self.tabSegmentedControl = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:#"Favourites", #"All", nil]];
self.tabSegmentedControl.frame = CGRectMake(20, 10, self.navigationBar.frame.size.width - 40, 30);
[self.tabSegmentedControl addTarget:self action:#selector(tabChanged:) forControlEvents:UIControlEventValueChanged];
[segmentView addSubview:self.tabSegmentedControl];
[self.navigationBar addSubview:segmentView];
[self.tabSegmentedControl setSelectedSegmentIndex:1];
}
The view and the SegmentedControl appear on the screen well, but they are not clickable. The selector doesn’t get executed when tapped on the SegmentControl; it doesn’t even switch tabs! In fact, the stuff that is underneath the segmentView (items in the TableView) get clicked when you tap on it. I have tried but failed to understand why this is happening! Any suggestions would be helpful!
You are adding a view below the bounds of its super view. You may see the view however you cannot click it because it is out of bounds. If you set the property of the navigation bar clipsToBounds to YES you should see that the view disappears. What you need to do is add the segment controller to the table view. Here is an example:
- (void)viewDidLoad
{
[super viewDidLoad];
...
[self.view addSubview: self.segmentView]; // need to keep a pointer to segmentView
self.tableView.contentInset = UIEdgeInset(self.segmentView.frame.size.height, 0,0,0);
}
-(void) scrollViewDidScroll:(UIScrollView*) scrollView{
CGRect rect = self.segmentView.frame;
rect.origin = self.tableView.contentOffset;
self.segmentView.frame = rect;
}

How to create a dropdown tableview like Vine App

I'm wondering how one would go about programming the kind of dropdown tableview that the Vine app uses. If you have never used Vine, I've provided a picture below which depicts the UI Design I'm talking about. Essentially, when you press the left hand UIBarButton, this tableview drops down. When you touch anywhere again, it drops down a little further (5 or 10 pixels) and then leaves the screen with a nice animation.
Just looking for some feedback on how I might go about implementing this. Thanks in advance.
Not sure a UITableView is the way to go about it.
Perhaps you can use REMenu available on Github to get inspired or fork it to customize to your needs.
The REMenu is as close to an exact copy as you can get. I did notice though that it wasn't clipping the top of the menu when it slid down, it slid underneath the status / nav bar which to me didn't look right. Without having looked at the sliding logic (and with my impressive SE reputation of "8"), this is my quick take on how you make the menu appear.
create a view for the contents of the menu (the table view etc)
put it in an enclosing menu collapsed to a zero height, with the content sticking off the top of the collapsed menu view
set the menu view to clip the contents so the top of the menu is not visible, then animate the contents down, as you animate the menu height larger.
This sample uses a simple gradient for the contents of the menu.
#interface BackgroundLayer : NSObject
+(CAGradientLayer*) redBlueGradient;
#end
#implementation BackgroundLayer
+ (CAGradientLayer*) redBlueGradient
{
CAGradientLayer *headerLayer = [CAGradientLayer layer];
headerLayer.colors =
#[(id) [UIColor redColor].CGColor, (id) [UIColor blueColor].CGColor];
headerLayer.locations = nil;
return headerLayer;
}
#end
#interface ViewController ()
#property (nonatomic, strong) UIButton* doIt;
#property (nonatomic, strong) UIView* menu;
#property (nonatomic, strong) UIView* nestedView;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// create simple toggle button to test the menu
self.doIt = [UIButton buttonWithType:UIButtonTypeRoundedRect];
self.doIt.frame = CGRectMake(50, 50, 50, 44);
[self.doIt setTitle:#"Doit!" forState:UIControlStateNormal];
[self.doIt sizeToFit];
[self.doIt addTarget:self action:#selector(doIt:)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.doIt];
// menu
self.menu = [[UIView alloc] initWithFrame:CGRectMake(20, 200, 280, 0)];
self.menu.layer.borderColor = [UIColor blackColor].CGColor;
self.menu.layer.borderWidth = 3.0;
self.menu.clipsToBounds = YES;
// menu contents
self.nestedView = [[UIView alloc] initWithFrame:CGRectMake(0, -100, 280, 100)];
CAGradientLayer *background = [BackgroundLayer redBlueGradient];
background.frame = self.nestedView.bounds;
[self.nestedView.layer addSublayer:background];
[self.nestedView clipsToBounds];
[self.menu addSubview:self.nestedView];
[self.view addSubview:self.menu];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction) doIt:(id) sender
{
if (!CGRectEqualToRect(self.nestedView.frame, CGRectMake(0, 0, 280, 100)))
{
[UIView animateWithDuration:0.15 animations:^{
self.menu.frame = CGRectMake(20, 200, 280, 100);
self.nestedView.frame = CGRectMake(0, 0, 280, 100);
}];
}
else
{
[UIView animateWithDuration:0.15 animations:^{
self.menu.frame = CGRectMake(20, 200, 280, 0);
self.nestedView.frame = CGRectMake(0, -100, 280, 100);
}];
}
}
#end
Cheers.
The Problem with the REMenu is, it creates the viewControllers every time the user taps on particular section, which should not be the case. It should persist the state of each screen attached there.

iOS UIScrollView in UIView

I have a little bit specific question. It might not matter for most people but I have had to deal with it and I had to solve the issue described below. I tried to find some information about it using Google and the Apple SDK documentation but did not succeed.
I was a designing a screen where there were many images in horizontal scrolls. There three three same scrolls. Every scroll had title. I have implemented custom class derived from UIView and placed there UIScrollView for scroll and UILabel for title text:
#interface MyView : UIView {
UIScrollView *iScrollView;
UIView *iTitleView;
}
I then put objects of this class on the view of a UIViewController:
#implementation MyViewController
- (void) viewDidLoad {
[super viewDidLoad];
...
iScrollViewTop = [[MyView alloc] init];
[self.view addSubview:iScrollViewTop];
...
}
#end
When I filled the internal scroll view with images and ran my application it looked OK. But there was some strange behavior. First, scroll did not have bounces as if I had set
iScrollView.bounces = NO;
and second, when I swiped to scroll, after the scroll stopped, the scroll bar did not disappear within one second. It was strange for me, because when I usually create a UIScrollView and add it to the UIViewController's view it has bounces and scroll bar disappears immediately when it stops. I tried to change UIScrollView's properties, such as directionalLockEnabled, pagingEnabled, canCancelContentTouches, delaysContentTouches, decelerationRate and others. In fact, I have tried to change almost all properties of UIScrollView but I could not get the scroll bars to immediately disappear.
If I try to add UIScrollView instead MyView to the UIViewController.view, it bounces and scroll bar disappears immediately after it stops. Also I get correct behavior if I subclass MyView from UIScrollView but in this case I cannot manage the title label because it scrolls together with other content.
So here are my questions:
Do you know why I am seeing this behavior?
How can I get "usual" behavior for scroll encapsulated by UIView?
ok, hacky code follows, so ignore all my other issues, but follow this pattern (westie.jpg = image that was 360x200)
#interface MyView : UIView
{
UIScrollView *sv;
UILabel *l;
}
-(MyView*)initWithFrame:(CGRect)frame;
#end
#implementation MyView
-(MyView*)initWithFrame:(CGRect)frame;
{
self = [super initWithFrame:frame];
sv = [[UIScrollView alloc] initWithFrame:CGRectMake(0,0,360,200)];
sv.scrollEnabled = YES;
sv.contentSize = CGSizeMake(360*3,200);
[self addSubview:sv];
UIImage *i1 = [UIImage imageNamed:#"westie.jpg"];
UIImageView *iv1 = [[UIImageView alloc] initWithImage:i1];
iv1.frame = CGRectMake(360*0, 0, 360, 200);
[sv addSubview:iv1];
UIImageView *iv2 = [[UIImageView alloc] initWithImage:i1];
iv2.frame = CGRectMake(360*1, 0, 360, 200);
[sv addSubview:iv2];
UIImageView *iv3 = [[UIImageView alloc] initWithImage:i1];
iv3.frame = CGRectMake(360*2, 0, 360, 200);
[sv addSubview:iv3];
l = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 100, 20)];
l.text = #"Hello World";
l.backgroundColor = [UIColor blueColor];
[self addSubview:l];
return self;
}
#end
later, in your outer view creation:
[window addSubview:[[MyView alloc] initWithFrame:CGRectMake(0, 20, 360, 200)]];

zooming animation problem in ScrollView - Ipad

In my app, I have a split screen in which the detail view is a scrollview. I have 5 tables which are subviews of my scrollview in which 3 table views are side by side on top and 2 table views are side by side on bottom
I have already implemented a way in which when I click any of the rows of any of the table in the scrollview, that view disappears and another view zooms into its position.
I write the following code in the didSelectRowAtIndexPath in the middle table subview,
CGFloat xpos = self.view.frame.origin.x;
CGFloat ypos = self.view.frame.origin.y;
self.view.frame = CGRectMake(xpos+100,ypos+150,5,5);
[UIView beginAnimations:#"Zoom" context:NULL];
[UIView setAnimationDuration:2];
self.view.frame = CGRectMake(xpos,ypos,220,310);
[UIView commitAnimations];
[self.view addSubview:popContents.view];
popContents is the view I need to zoom into to the view previously occupied by that particular table view and that happens correctly.
However the problem that I am facing is that since there is another table subview in the side, if I increase the frame size to say 250 or so, the part of the zoomed in view gets hidden by the tableview on the side ( as its as if a part of the zoomed in view goes under the tableview on the side).
Is there anyway to correct this so that my zoomed in view would not get hidden by the tableviews on its sides?
I hope I have explained my problem correctly...
UPDATE:
Here is the code I am using for adding the subviews for the scrollview
// Scroll view
scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 30, 1000, 740)];
scrollView.contentSize = CGSizeMake(1000, 700);
scrollView.delegate = self;
scrollView.scrollEnabled = YES;
scrollView.showsHorizontalScrollIndicator = YES;
scrollView.backgroundColor = [UIColor scrollViewTexturedBackgroundColor];
[self.view addSubview:scrollView];
aView = [[aViewController alloc] initWithNibName:#"aViewController" bundle:nil];
aView.view.frame = CGRectMake(10, 25, 220, 310);
[aView loadList:objPatients];
[scrollView addSubview:aView.view];
bView = [[bViewController alloc] initWithNibName:#"bViewController" bundle:nil];
bView.view.frame = CGRectMake(10, 350, 220, 310);
[bView loadList:objPatients];
[scrollView addSubview:bView.view];
cView = [[cViewController alloc] initWithNibName:#"cViewController" bundle:nil];
cView.view.frame = CGRectMake(240, 25, 220, 310);
[cView loadList:objPatients];
[scrollView addSubview:cView.view];
dView = [[dViewController alloc] initWithNibName:#"dViewController" bundle:nil];
enView.view.frame = CGRectMake(240, 350, 220, 310);
[enView loadList:objPatients];
[scrollView addSubview:dView.view];
eView = [[eViewController alloc] initWithNibName:#"eViewController" bundle:nil];
eView.view.frame = CGRectMake(470, 25, 220, 310);
[eView loadList:objPatients];
[scrollView addSubview:eView.view];
say for example, I add the code for didSelectRowAtIndexPath in cViewController subview...
This is a guess since I would need to know how your table views are added to the scroll view, but the middle table view was probably added before the one on the side. Views are "stacked" in the order they're added with the last one on top. You'll need to get the scroll view to move the middle view to the front with this method
- (void)bringSubviewToFront:(UIView *)view
The best way to do that would be to create a protocol for the table views and make the scroll view the delegate. The method would be something like this
- (void) moveAViewToFront: (MyTableView *) aTableView
{
[self.view bringSubviewToFront: aTableView.view];
}
You would then call the delegate method before setting up the animation.
Edited
After a little more thought I realized that the subviews have a reference to their superview so this bit of code should provide an idea on how to solve the problem. I created a test app which has a view controller which adds two sub views. The view controller header file is MoveSubviewViewController.h
#import <UIKit/UIKit.h>
#interface MoveSubviewViewController : UIViewController
{
}
#end
and it's implementation is
#import "MoveSubviewViewController.h"
#import "MoveableSubview.h"
#implementation MoveSubviewViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Create two overlapping subviews. The blue subview will start at the top of
// the frame and extend down two thirds of the frame.
CGRect superviewFrame = self.view.frame;
CGRect view1Frame = CGRectMake( superviewFrame.origin.x, superviewFrame.origin.y,
superviewFrame.size.width, superviewFrame.size.height * 2 / 3);
MoveableSubview *view1 = [[MoveableSubview alloc] initWithFrame: view1Frame];
view1.backgroundColor = [UIColor blueColor];
[self.view addSubview: view1];
[view1 release];
// The green subview will start one third of the way down the frame and
// extend all the to the bottom.
CGRect view2Frame = CGRectMake( superviewFrame.origin.x,
superviewFrame.origin.y + superviewFrame.size.height / 3,
superviewFrame.size.width, superviewFrame.size.height * 2 / 3);
MoveableSubview *view2 = [[MoveableSubview alloc] initWithFrame: view2Frame];
view2.backgroundColor = [UIColor greenColor];
[self.view addSubview: view2];
[view2 release];
}
#end
The subview class is MoveableSubview with another simple header
#import <UIKit/UIKit.h>
#interface MoveableSubview : UIView
{
}
#end
and implementation
#import "MoveableSubview.h"
#implementation MoveableSubview
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// Move this view to the front in the superview.
[self.superview bringSubviewToFront: self];
}
#end
The thing to do is to add the
[self.superview bringSubviewToFront: self];
line before setting up the animation.

Resources