I wrote a little UIView subclass to show a progress HUD. That HUD view works perfect but I faced a little problem in the last days.
In my application I'm presenting a UIViewController in a custom way. When the user selects a row in a tableView I'm creating an instance of my second viewController, move it to the current VC, set it's view's height to zero, add it as subview of the curren VC's view (at the position of the selected cell) and animate the height back to original.
The behaviour looks pretty cool and works great.
But when the second view is added as subview, I'm adding a HUD to this view. When the second view is resizing to the original height, the HUD sticks to the top of the view and is just a few pixels high:
I played around a bit with NSLayoutConstraints... But I didn't get it working until now...
Has someone a good idea on that one? Or does anybody know good and well explained resources on these constraints?
The HUD is actually a background view with the little window as subview. All other elements (the progress view, labels and so on) are subviews of the little window.
In terms of good resources, I definitely recommend WWDC2012's 3 videos: Introduction to Auto Layouts for OSX/iOS, Auto Layout by Example, and Best Practises for Mastering Auto Layout. These have some tips for looking at ambiguity in the layout and dealing with conflicts
Another great reference is Erica Sadun's iOS6 recipe book.
Re your problem. I'm assuming that you're not seeing an error message and you're laying out the progress HUD entirely in the XIB. If so, it sounds like you have two constraints that aren't behaving as you'd like from the xib - the height from the top of the superView and the height of the HUD
Firstly, create an outlet for the constraints to the .h file
#property (strong) IBOutlet NSLayoutConstraint *HUDSuperViewToHUDConstraint;
#property (strong) IBOutlet NSLayoutConstraint *HUDHeight;
Next, in the method in which you open the new viewController with the HUD remove the constraints so that there is no conflict when you first show the new view
[HUDSuperView removeConstraint:self.HUDSuperViewToHUDViewConstraint];
[HUDSuperView removeConstraint:self.HUDHeight];
After you've called [HUDSuperView layoutIfNeeded] for the first time, in the animation or wherever, add the constraints and call layoutIfNeeded again
[HUDSuperView addConstraint:self.HUDSuperViewToHUDViewConstraint];
[HUDSuperView addConstraint:self.HUDHeight];
[HUDSuperView layoutIfNeeded];
If you call these within an animateWithDuration it may even animate the appearance...cheesey
Hope this helps - it's probably more of a step toward the solution rather than the solution itself. Recommend minutes 17 and 53 in the AutoLayout by Example video too.
Steve
Related
I have a nib that has the standard UIView and I've also included a UIScrollView as an IBOutlet (yes, it is hooked up), not as a subview of the main UIView, but just out on its own. Autolayout is turned off. The scroll view has several subviews and is larger than the main view. In viewWillAppear:, I set the content size of the UIScrollView to its current frame size, and then set its frame to the size of the main view, and add it as a subview of the main view.
Unfortunately, nothing is showing up. When I NSLog the frame of my UIScrollView, it is coming back as {0, 0, 0, 0} (CGRectZero). I thought this was odd, so I went back and tried logging the frame before I do any changes to it. Still zeroes. Logged it out in viewDidLoad before anything is done to any of my view elements. Still zeroes. (FWIW in my nib, the frame is {0, 0, 320, 896})
I've had this issue with several of my controllers, but it seems to be hit or miss. Sometimes it works, other times I get the empty frame. Typically, recreating everything from scratch seems to fix the issue, but I don't know why, as I'm setting everything up the same both times.
Running Xcode version 6.1 (6A1052d), iOS SDK 8.1 with a deployment target of 7.0
Let me know if there is any other relevant information I can give that might help.
EDIT 1: To address the questions about my UIScrollView being a "subview", here is what my view heirarchy looks like in the document outline:
As you can see, the UIScrollView is a "subview" of the view controller, but is not a subview of the "main" UIView which has the controller's view outlet.
EDIT 2: More images and some code. Here is a better look at how my nib is set up:
I add my scrollView to the main UIView as follows:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
scrollView.contentSize = scrollView.frame.size;
scrollView setFrame:CGRectMake(0, 100, self.view.frame.size.width, self.view.frame.size.height - 100); // I slightly modified this because there are other variables that determine its Y position and height
[self.view addSubview:scrollView]
}
But I still don't think any of that code matters- for whatever reason, my UIScrollView is coming back nil. If I have the view created in my nib, the outlet is connected properly, how would the view still be nil? I'm creating my controller via initWithNibName I've tried cleaning the project, and removing the app and reinstalling.
Best guess on an answer, and some comments:
Nibs are simply serialised object trees. You can have as many view as you want as root. The problem (big one!) is that if they are not "connected" to outlets that retain them, at some point you will loose those references.
Since your UIScrollView is not a subview of the main view controller view on the Nib, it's not retained by it (views retain their subviews). So, it's up to you to retain it on your view controller.
My guess is that at the point you are trying to set the frame in the View Controller, the scroll view is already gone. I'm not sure how sure when are you doing it, but it might be after some run loops, so the unretained scroll view is dealloced.
Workarounds:
Instead of a variable for the IBOutlet, use a strong property (#property (strong, nonatomic) IBOutlet UIScrollView *scrollView).
Add the __strong qualifier to the variable.
Add the UIScrollView as a subview within the nib, so when the object tree is deserialised it's already retained by its parent.
Given that you purposefully put the UIScrollView outside of the main view hierarchy, I'm assuming you don't want option 3, so I'll just go with 1.
First, you might want to consider using autolayout. It makes things a lot easier once you make the investment to understand how it works. Check out this Apple doc on using UIScrollview with autolayout.
If that's not an option, the problem is that you're setting up your scrollView at the wrong point in the cycle. The scrollView needs a chance to lay itself out. By the time you hit viewWillAppear, you've missed sub view layout.
So, you could try:
Configure your scrollView in willLayoutSubviews and set its setNeedsLayout property.
Explicitly call layoutIfNeeded on your scrollView in viewWillAppear. The idea is to force another subview layout cycle. But, I'm not 100% sure it would work -- it might be too late at that point.
Note that the size of the main view (self.view) isn't determined until viewDidAppear. It's not always correct in viewWillAppear. So, you have a problem in your setup: you can't really set up your scrollView until you know your view size, but you don't know that until it's too late! You might want to redesign to avoid that dependency.
This one is really bugging me! What is the name of this UIView subclass? I'm not talking about the compass itself, but the two dots at the bottom of the view. I know it's not a private API because I have seen it before. Or am I confused and this is not a UIView at all, but a UIViewController. Which UIView / UIViewController subclass is shown here. It acts like a UIScrollView, but has distinct pages, and has the dots at the bottom of the screen that show the users relative progress through the pages. I have checked this link about UIView subclasses, but became lost after about the 45th one. http://www.themusingsofalostprogrammer.com/2010/09/list-of-every-uiview-subclass.html
(source: tqn.com)
Thankyou for your time.
It is a UIPageControl. It corresponds (or is supposed to correspond) to the number of "pages" the user can scroll to, sideways. Normally, it indicates how many pages there are, and which one we are on, plus it typically provides a way to scroll sideways (by tapping to its left or right).
If I may add to what matt said...
To use a UIPageControl effectively, you also need a UIScrollView that contains the content. An update to the page control should result in a change to the contentOffset of the scrollView as shown in the code below. UIScrollView has a pagingEnabled property that should be set to YES to complete the illusion of paging.
- (IBAction)pageValueChanged:(UIPageControl *)sender
{
// self.pagedView is an IBOutlet to a UIScrollView
[self.pagedView setContentOffset:CGPointMake( sender.currentPage * 320, 0 ) animated:YES];
}
I have a requirement in my app to display a bunch of information that includes both text and images. It will be quite long, so it will need to be scrollable to access all the content.
I know that I can achive this by programmatically adding different UILabels, UIImages etc to a UIScrollView. But this is a proof of concept, so I'm looking for something a little quicker than having to work out all the positioning and code required. The information is static anyway, and does not need to interact with code.
Is there a way to do this using the interface builder (storyboard or xib is fine)?
you definitely can do that if you simply want a quick interface
1.> you might need to know how long is your scroll view, for example in my case, i set it to 1568
2.> Then i drag all the controls that will fit for the first 568 pixel view onto the scroll view and position them.
3.> Then change the Y value for that scroll view to something like - 500, so you can see the rest of the scroll view, and put everything you need there.
4.> After you have all your controls, and remember to set the frame back to 0,0,320,568
5.> last step, in your code, set SCROLLVIEW.contentSize = CGSizeMake(320, 1568);
I would still suggestion don't hard code all those values, but if you are looking for a quick way to do your interface, hope that gives you some ideas.
Just start a new project with a single view, it will come with a xib or storyboard for its single ViewController.
Create a UIView by dragging it into the workspace and place as many Labels, Images and UI Elements as you want.
Open the xib / storyboard and drag a UIScrollView in as your root VC's root view. Drag the view containing your layout into the scrollview, making it the scrollviews only subview.
Done (almost)!
If you launch your app at this point, you'll notice you can't scroll. That is because the scrollview is "too stupid" to adjust the size of its contentSize property on its own.
You'll need some code here, but it is only a tiny snippet and you won't need to touch it again:
Create a new Category on UIScrollView.
In your category's implementation, do:
#implementation UIScrollView (MyHandyCategory)
-(void)awakeFromNib {
NSArray *subViews = [self subviews];
UIView *contentView = [subViews objectAtIndex:0];
[self setContentSize:contentView.frame.size];
}
#end
Done (for real this time)! This will check the size of the view your scrollview contains and ajust the contentSize after it has been initialized. You can change the size of your content view as you like, no need to play around with hardcoded values or even Interface Builder values!
If it’s just proof of concept I’d have a WebView and a local HTML page you load. Easy-peasy.
I would suggest UICollectionView. It's fairly straightforward. There's a good tutorial here:
http://www.raywenderlich.com/22324/beginning-uicollectionview-in-ios-6-part-12
I want to create a footer fixed to the bottom of the viewport in an iOS app I'm developing (it's my first one, I come from a web dev background). However I'm having difficulty finding information on how to go about this.
What I have in mind is essentially just 3 rectangular buttons next to each other that are just constantly fixed to the bottom of the viewport. I tried dragging a toolbar into my scene as a starting point, but in the interface builder it's placing itself under my table cell, rather than fixing to the bottom. I feel like this is probably easier to do programmatically, but I just don't know where to start.
If someone could at the very least guide me in the right direction I'd really appreciate it. Thank you.
Okay... create/add custom UIView on your main view (self.view)
and set this custom view's frame = (0, 0, 320 , self.view.frame.size.height - 50) /// change value 50 to as per your requirement.
and add all of then UIControls such like textField, tableVie, button ...etc.. on this custom view.
Don't use a UITableViewController. Use a UIViewController, then add a table view and a toolbar. You'll need to implement the UITableViewDelegate protocol in code and then connect the delegate in interface builder.
Within the pageViewController:viewControllerAfterViewController: method, just before the return statement, the view which is about to be returned as the next page has the correct view frame size.
However immediately after the pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted: method is called, I check the frame size of the newly introduced view controller ([pageViewController2.viewControllers objectAtIndex:0];) and I find it resized.
Note that, I have set [self.pageViewController.view setAutoresizesSubviews:NO] and the autoresizing mask to None for the newly created ViewController.
Any ideas in which step the new ViewController is being resized?
I think the problem is inherently related to the nature of UIPageViewController. It is built from UIScrollView. I don't exactly know why there is strange resizing behavior, but it seems to be particularly pronounced when the view controllers that make up your pages use auto layout. Seemingly, locking the constraints in your page view controllers to the superview makes the elements resize after the transition because the superview is itself getting resized after said transition.
This sucks because Apple is basically pushing all of us to adopt auto layout. Auto layout is awesome, and I recommend everyone use it from now on, but it really really sucks when you use it with a UIPageViewController. They really ought to either scrap that class or build something easier for developers, something that can be dragged into a storyboard outright.
A few things to consider.
1.) Don't lock anything to the "Top Layout Guide" or the "Bottom Layout Guide". Also make sure you have "Constrain To Margins" disabled on any view intended to hug the sides of the screen.
2.) If you are using a label in your individual page / content view controllers, make sure you bind/constrain it to something other than the superview. I wanted to place a label over a UIImageView, so I aligned the label to the leading and top edges of the image view (using AutoLayout constraints only), creating an offset to give the label some margins.
3.) The following would otherwise be a good tutorial. However, it is a bit outdated. I downloaded the project and basically modified it to get a UIPageViewController implementation that works. The only problem with this project is that it doesn't use AutoLayout. I'm currently writing a blog post that more clearly discusses how to use UIPageViewController and Autolayout together.
http://www.appcoda.com/uipageviewcontroller-storyboard-tutorial/