im trying to learn how to perform custom segues so i can have a game menu, and when i searched on the web it seems that you have to make a custom segue class, and override -(void) perform and in that method you have to specify a made up destination vc and a source vc. and establish its location and stuff. This was the one of the code things i saw on the internet.
#implementation FromTopReplaceSegue
-(void)perform{
UIViewController *dst = [self destinationViewController];
UIViewController *src = [self sourceViewController];
[dst viewWillAppear:NO];
[dst viewDidAppear:NO];
[src.view addSubview:dst.view];
CGRect original = dst.view.frame;
dst.view.frame = CGRectMake(dst.view.frame.origin.x, 0-dst.view.frame.size.height, dst.view.frame.size.width, dst.view.frame.size.height);
[UIView beginAnimations:nil context:nil];
dst.view.frame = CGRectMake(original.origin.x, original.origin.y, original.size.height, original.size.width);
[UIView commitAnimations];
[self performSelector:#selector(animationDone:) withObject:dst afterDelay:0.2f];
}
- (void)animationDone:(id)vc{
UIViewController *dst = (UIViewController*)vc;
UINavigationController *nav = [[self sourceViewController] navigationController];
[nav popViewControllerAnimated:NO];
[nav pushViewController:dst animated:NO];
}
#end
i guess this is supposed to make a segue that appears from the top going down or something. but i have a few questions like, what is a source and dest vc, and also in the part where the code says dst.view.fram=CGRectMake(dst.view.frame.origin.x, 0-dst.view.frame.size.height,
what the heck is that 0 there, and shouldnt that be dst.view.frame.origin.y ?
well, anyways i wired this up and created a push segue from a button, but when i did this the segue only came to about 3/4 down from the top of the screen, and the bottom 1/4 was showing the bottom of my root view. and also when i tried pressing a button on my new vc after the segue the program crashes.
Any Info would help Please!
This is one of those things that aren't fully explained it looks like. Judging from your code, it looks like the "source view controller" is the root view controller(or parent view controller) and the "destination view controller" is the DetailViewController(or the child view controller).
Not sure if this confuses you more. Anyways, the parent/root view controller is the main view controller and it handles displaying of child view controllers. What I find interesting is that the creator subclassed the view of another class rather than presenting it.
Now 0 - self.view.frame.size.height = -320 on the y axis. Think of your main view as a piece of paper and think of the child view as a second piece of paper. Imagine placing the two pieces of paper one above the other(not on top.) The screen will only display the bottom piece of paper until you animate the "paper above it" down. Basically, he plans to animate from the top rather than the bottom(I guess I answered my own curiosity.)
Related
I'm trying to create simple (facebook like) menu in my app. I've found several questions, most of them had accepted answer, but usually answer is to use some programs done by developers.
These programs are often in old version of xCode (one, that didn't use storyboards) and those, that were done in storyboard, were too complicated for me to implement in my app.
So I found one question, which had as an answer something like this:
1. Create your menu view controller (UITableViewController for me)
2. Hide this controller in your initial view controller :
MenuViewController *menView = [self.storyboard instantiateViewControllerWithIdentifier:#"Menu"];
[self.view sendSubviewToBack:menView.view];
3 Create a button (maybe pan gesture later), in it's method, do something like this:
-(IBAction)menuButtonPressed:(id)sender
{
CGRect destination = self.navigationController.view.frame;
if (destination.origin.x > 0) {
destination.origin.x = 0;
} else {
destination.origin.x +=254.5;
}
[UIView animateWithDuration:0.25 animations:^{
self.navigationController.view.frame = destination;
} completion:^(BOOL finished)
{
self.view.userInteractionEnabled = !(destination.origin.x > 0);
}];
}
What this does I guess, it's that it basically moves your view to the right by fixed length. I suppose, that your view, that you sendedToBack (your UITableView) should be exposed and interactable. However, all it does for me, is moving top view to the right, and leaving blank black-colored screen behind.
Now I think, that my problem is either bad instantiation of menuView, or just that I understanded this guide wrong.
If anyone knows, how to deal with this, I would be very thankful for an answer. Maybe there is some already done app, that is as easy as this to understand and hopefully easier to implement in my code :)
Thanks!
I would do this using container views in storyboard. You can size the left one how you like, then add a right one next to it, and in the inspector change its width to 320 -- most of it will go off the screen to the right, and it will resize its embedded controller to be full screen size. You can delete the view controller that you get with the left container view, then drag out a table view controller, and connect it from the left view with an embed segue (it will be the only choice when you control drag from the container view). I added two swipe gesture recognizers (one left and one right) to the main controller's view and connected them to the 2 methods in the main controller. Then in the main controller I have this code in the .h:
#interface ViewController : UIViewController
#property (weak,nonatomic) IBOutlet UIView *rightView;
#property (assign,nonatomic) CGRect rightRect;
#end
And in the .m, only this:
#implementation ViewController
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.rightRect = self.rightView.frame;
}
-(IBAction)revealSidebar:(id)sender {
[UIView animateWithDuration:.3 animations:^{
self.rightView.frame = self.view.window.frame;
}];
}
-(IBAction)hideSidebar:(id)sender {
[UIView animateWithDuration:.3 animations:^{
self.rightView.frame = self.rightRect;
}];
}
#end
I have a view-controller that I'm re-using (for memory limitation reasons.) So, rather than push a new UIViewController, I just set a few parameters, then force this VC to reload its view. The code is something like this (triggered by a Notification callback):
- (void) reloadView: (NSNotification*) note
{
// Save parameters for reloaded view
UIWindow *window = (UIWindow*) self.view.superview;
//CGAffineTransform xfrm = self.view.transform; // Doesn't do what I want.
// Trash this one & reload the view
[self.view removeFromSuperview];
self.view = nil;
// force view reload
if (self.view == nil)
{
NSLog(#"%s ***** Why didn't self.view reload?!", __PRETTY_FUNCTION__);
}
else
{
// restore params
//self.view.transform = xfrm; // Boo-hoo!
[window addSubview: self.view];
}
}
Everything works fine except that the app is landscape only, and the newly reloaded view is added to the Window as portrait.
I tried forcing the old view's transform onto the new view but, oddly, it gave the rotation but a goofy translation offset.
Is there a way to tell a UIViewController "do your rotation, now"...?
EDIT:
I added this rather silly hack:
// restore params
self.view.transform = xfrm;
self.view.center = CGPointMake(window.bounds.size.width / 2., window.bounds.size.height / 2.);
[window addSubview: self.view];
which gives the desired result, but I'm really displeased with having such a thing in my code base. Surely there's a better way to do this?!?!
Thanks!
EDIT:
After some discussion with JPH, then answer turned out to be "don't do things that way." See comments for some of the details and the redesign that took place.
Your problem is in using this:
[window addSubview: self.view];
From the documentation:
If you add an additional view controller's UIView property to UIWindow
(at the same level as your primary view controller) via the following:
[myWindow addSubview:anotherController.view];
this additional view controller will not receive rotation events and
will never rotate. Only the first view controller added to UIWindow
will rotate.
https://developer.apple.com/library/ios/#qa/qa2010/qa1688.html
I would much prefer a design with a root view controller and the subviews being added to the root view controller's view.
Another option is to NOT kill the view and re-add it, but rather update everything that needs to be updated in that view. I am not sure I understand why you would want to kill a view and re-add it right away.
I have created a Cartview and want to display this view as a modalview when i click a button on productview.How can i do this ?
Actually i did this like
UIViewController *nav=[[UIViewController alloc]initWithNibName:#"CartView-iPad" bundle:nil];
nav.modalPresentationStyle=UIModalPresentationFormSheet;
nav.modalTransitionStyle=UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:nav animated:YES];
CGRect frame=nav.view.frame;
frame.origin.x -= 75;
frame.origin.y = 100;
nav.view.frame=CGRectMake(frame.origin.x, frame.origin.y , 672, 393);
But the problem is the formsheet view is coming and my cart view is coming overt that i need only my cartview.Also i need a close button on the right to side of the cartview to dissmiss the modalview.
The problem may be that you're trying to set the frame of the modal view controller manually. This is, as far as I know, not recommended. The UIModalPresentationFormSheet option already indicates the desired size of the modal.
As for a back button, you should add a navigation bar with a back button in your CartView-iPad-xib-file. To make it work you have to make a subclass of UIViewController (example: CartViewController) which will handle the back button press. Right now nav is just a normal UIViewController which has no idea what to do with the actions in your xib-file.
Then in your new view controller, you can make a function like this that you connect your back button to:
- (IBAction)backButtonPressed
{
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
#Jonah pointed me to the following explanation he has provided:
Question on View Controller's view
I currently have a MainViewController whose main(root?) view contains an Add Record UIButton which is linked to the following method and is dependent on which page the user currently has open in a UIScrollView:
#pragma mark -
#pragma mark Add View Support
- (void)add:(id)sender {
MyAddViewController *controller = nil;
// Dynamically creating and allocating the Add View Controller
if (dataViewPageControl.currentPage == 0) {
controller = [[MyAddViewController alloc] initWithAddType:1];
} else {
controller = [[MyAddViewController alloc] initWithAddType:0];
}
controller.delegate = self;
// Slide the new AddViewController's view in from the right
CGPoint initialCenter = self.view.center;
controller.view.center = CGPointMake(initialCenter.x + 320, initialCenter.y);
[self.view addSubview:controller.view];
[UIView beginAnimations:#"animation" context:NULL];
[UIView setAnimationDuration:0.35];
[UIView setAnimationDelegate:controller];
controller.view.center = CGPointMake(initialCenter.x, initialCenter.y);
[UIView commitAnimations];
}
MainViewController is the delegate for MyAddViewController (which has a CANCEL/CLOSE button) and when it's close button is clicked the MyAddViewController is released through the close method in this MainViewController.
As you can see, MyAddViewController's view becomes a subview to the current root view (Bad? Bad? Bad?) MyAddViewController's view is as a FULL size view (320x416) but whose top half is transparent, so that MainViewController's view can be seen below on the top half and the bottom half contains the AddViewController's view and textfields etc....
Based on what I've read from #Jonah just now at the link at the very top this sort of design is BAD, BAD, BAD. I've noticed when testing in Instruments that MyAddViewController sometimes lags when sliding in. I'll click the ADD button to fire this method and there is sometimes a 3 - 5 sec delay before the view slides in. Some sort of delay in all the memory management and cleanup?
I'm assuming I can fix this by making a single MyAddViewController property inside MainViewController which will then prevent me from allocating and deallocating on every button click. That should make things smoother? My only worry is that this will cause a greater memory imprint at application load time. Instruments seems to show a 516KB allocation every time I click ADD for the first time in my app.
Any recommendations though on design and how to improve this? The reason I like having separate view controllers with nested views like this is because MyAddViewController has a lot of logic with Core Data and such and MainViewController is pretty busy enough already as it is. Trying to keep things modular.
Thanks in advance!
I think the best solution is to redesign your MyAddViewController to be a controller which inherits from NSObject rather than UIViewController. The MyAddViewController can then manage a subview of your MainViewController allowing you to keep your controller logic nicely encapsulated without abusing UIViewController.
I've tried to describe why nesting the views of multiple custom UIViewController classes is problematic here: http://blog.carbonfive.com/2011/03/09/abusing-uiviewcontrollers/
An add view controller is the perfect type of thing to use modal presentation for. Instead of handling all that animation yourself, just present it like this:
[self presentModalViewController:controller animated:YES];
Edit
Then I would recommend making MyAddViewController a property on MainViewController and write a custom accessor. Additionally I would defer setting it's addType (if possible) until the point at which you will actually animate the view in. This way we can just have one generic MyAddViewController hanging around, instead of having to create a new one every time the detailPageControl changes.
- (MyAddViewController *)addViewController
{
if (!addViewController)
addViewController = [[MyAddViewController alloc] init];
}
Then before you animate it...
self.addViewController.addType = 0;
if (dataViewPageControl.currentPage == 0) {
self.addViewController.addType = 1;
}
I am presenting a modal view controller. If it matters, it is scrolling up from the bottom. How can I control what portion of the screen it occupies?
EDIT: I have the following in the modal view controller. It's not helping.
- (void)viewDidLoad {
TestResultView *trv = [[TestResultView alloc]initWithTest: [Model m].currentTest];
self.view = trv;
trv.frame = CGRectMake(0, 320, 320, 160);
[trv release];
[super viewDidLoad];
}
You can modify the frame of the view controller, but if you're using UIViewController's -presentModalViewController:animated: method, the view behind will be unloaded once your modal view is finished animating onto the screen (This assumes you're on an iPhone) and you'll see a white screen where your background view should be. iOS assumes that your modal view controller will be a full-screen view controller, and dumps the other view to save memory.
If you really want to show a view over part of the screen, you should instead add the UIView (no UIViewController) to your current UIViewController's view as a subview, and then animate it onscreen yourself. I think something like this would work in your UIViewController class that will present the view:
// Add the view as a subview and position it offscreen just below the current view
UIView *myHalfView = [[UIView alloc] initWithFrame:someAppropriateFrame];
[self.view addSubview:myHalfView];
CGRect offScreenFrame = myHalfView.bounds;
offScreenFrame.origin = CGPointMake(0.0, CGRectGetMaxY(self.view.frame));
// Now animate the view upwards
[UIView beginAnimations:nil context:nil];
// Move the view upwards the height of your sliding view so it's entirely onscreen
myHalfView.center = CGPointMake(myHalfView.center.x, myHalfView.center.y - myHalfView.bounds.size.height);
[UIView commitAnimations];
[myHalfView release];
For bonus points, you could fade the view in by setting
myHalfView.alpha = 0.0;
before the UIView animation block, and setting
myHalfView.alpha = 1.0;
inside the block after animating the center property.
When you're done, you can do something similar but in reverse to slide the view offscreen. You can add an animationDidStop selector to the UIView animation block to be notified when the view has slid off screen so that you can remove it from the view hierarchy.
From an aesthetic point of view, you should also be careful how you do this since having a view slide up is a standard behavior, and if your view looks like a normal view but stops halfway, users may feel (even briefly) that the app has frozen. They'll figure it out, but it will leave a bad feeling about your app if not handled carefully. Mainly, I would avoid using standard full-screen cues like including a UINavigationController at the top of your view to help users understand what's going on. Half-sheets tend to be UIActionSheets on the iPhone, so think in that direction.
That is nice, the above accepted answer explains a nice hack to present subViews which feel like ModalViews, but what if it is an iPad, and i can indeed give it a modalViewController which doesnt cover the entire screen.
In case of iPads, I dont think the underneath view will be unloaded. ( because there are options where we can present the modalView on iPads, which dont cover the entire screen )
ModalViewController in the end is a controller itself, and like any other controller has a root view, whose properties can be editted, if we can get hold of it.
Here is what will give you a custom frame of the ModalView :
MyViewController *viewController = [[MyViewController alloc] init];
viewConroller.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:viewController animated:YES];
//superView of viewController's view is modalViewController's view, which we were after
viewController.view.superview.frame = CGRectMake(x,y,w,h);
//x y w h - can have desired values.
I would add to #dsaw's answer that the superview of the modal view does not seem to rotate its coordinate system in landscape mode. Here is the code that I used in my own app:
MyViewController* modalVC = [[MyViewController alloc] init];
modalVC.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:modalVC animated:NO];
CGRect r = CGRectMake(self.view.bounds.size.width/2 - 236,
self.view.bounds.size.height/2 - 130,
472, 260);
r = [self.view convertRect:r toView:modalVC.view.superview.superview];
modalVC.view.superview.frame = r;
While the superview may not rotate itself with the iPad, it does seem to do the right thing and keep the modal view centered if I rotate the iPad after showing the modal view.