Best time in View lifecycle to determine the on-screen size of a UIScrollView - ios

I have a .xib containing a view which itself containing top and bottom accessory views (navigation, a page control) and a UIScrollView. Its height varies depending on whether I'm running on an iPhone 4 or 5 (3.5" or 4"). At runtime I move the containing view offscreen, populate the scrollview with UIButtons to build a scrollable launchpad, and scroll this on to the screen from the bottom. Tapping a button shows another view.
On a smaller screen I'd like to show only three buttons in the scrollview, on the larger one I show four. So I need to know the height that the scrollview will be when it appears, before it actually appears. I'd hoped to have this information available at some point in the view lifecycle (viewWillAppear etc.) but the only place this seems to be correct is in viewDidAppear. At this point the view is already on screen and creating the buttons then means they appear suddenly, and are not scrolled nicely onscreen.
The top-level view in the .xib is set to Retina 4 FullScreen.
I'm happy to concede that viewWillAppear is the correct place to create the buttons and my navigation controller is perhaps instantiating the view incorrectly, but my iOS-fu isn't strong enough to say for certain.
My solution has been to detect screen size and hardwire the button height. Is there a better (more elegant, future-proof and canonical) way to do this?

Don't worry, you are in the right direction:
viewWillAppear is the correct method in a view's lifecycle, where the frames of that view and its subviews are completely calculated and nothing has been drawn yet. Here you can make corrections to the standard behavior of your view.
When viewDidAppear gets called, you get the same information about positions and sizes of view elements as in viewWillAppear. But at this time, the animations and the view itself already have been drawn.
That is, why you'll want to use viewWillApear.

Related

One of my UICollectionViews is not scrolling or responding to touch events inside my UIScrollView container [Puzzling]

I have an unusual and challenging problem. I have researched deeply on StackOverflow and have been unable to find any solutions. Please do not ask about my design style- it must be this way.
I have a UIPageViewController that contains a UIScrollView which contains 4 UICollectionViews. Each of these collection views should be horizontally scrollable but not vertically scrollable. The scroll view is necessary because the screen is not large enough to display all 4 collection views. Upon loading the screen, the 4th collection view is not immediately visible. After scrolling down, the 4th collection view then becomes visible.
The problem I am having is the 4th collection view does not respond to touches. Specifically, it does not respond to taps or attempts to scroll. The other 3 work perfectly fine. What makes this puzzling and odd is that the 4th one is exactly the same as the other 3, the delegate and data source are set properly and user interaction is enabled. The only real difference between the problematic collection view and the others is that it is not immediately visible when the screen loads.
When I attempt to scroll it behaves as if I am trying to change the page, so the UIPageView changes the page. So the CollectionView isn't registering any touches at all. I have a hunch that it either has to do with the GestureRecognizer from the PageView or something to do with how it isn't visible on the screen upon the initial load.
Any thoughts?
Not 100% sure without seeing the initialization of code or storyboard, but the best thing is that maybe there is a view overlapping. You can run your program and click on the 2 overlapping rectangles. This is the Debug View Hierarchy. It shows you your current frame on your phone and you can see the view laid out in hierarchal status.
Solved my own problem thanks to impression7vx's mention of the Debug View Hierarchy. Had no clue this existed but it let me discover the flaw. I am just writing this to help people who stumble upon this in the future.
I had a content view inside the ScrollView that had a problematic constraint. I set it to have "Equal Heights" with the ScrollView but upon loading the screen the height was static. Only half of the scroll view showed and that half would be registered as the height for the UIView. Since it was static the view would not become larger when I'd scroll, and the 4th collection view would not be inside that container view anymore. For some reason, since the collection view wasn't in the container view it wouldn't register touches.
Solved by manually making the height the same value of the scroll view! This allowed the content view to contain all of the collection views which made them behave normally again.
Just take NSLayoutConstraint of height of the UIView inside the UIScrollView
#IBOutlet weak var scrollContentViewHeight: NSLayoutConstraint!
Set height of this scrollContentViewHeight equal to height of dynamic size of the UIScrollView
if scrollableView.contentSize.height = 840.0
then
scrollContentViewHeight.constant = 840.0
set it on viewDidLayoutSubviews
override func viewDidLayoutSubviews() {
scrollableView.contentSize.height = 840.0
scrollContentViewHeight.constant = 840.0
}

Scrollview won't scroll all the way down

I have a scroll view which works fine. Almost. I'm building in Any/Any. The problem is that the scroll view won't scroll past the view controller. I have a switch that is mostly in the view controller window, but the rest is off the box (not really sure how to describe it; it's in the view in the scroll view, but the view is longer than the view controller so part of it is hidden).
The scroll view will scroll down until it hits the part where the view controller would end if you were looking at it in Xcode. There is some more stuff under the switch (labels and another switch). To view these you have to forcefully scroll down. Xcode shows no constraint errors (little red circle with white arrow).
Hopefully this makes sense
A ScrollView needs to know the height and width of the content it is holding in order to know how much to scroll and which direction. Here is a quick read on how ScrollViews work in iOS: https://www.objc.io/issues/3-views/scroll-view/
You can set this programmatically using the contentSize property, but this requires you to know and or calculate the contentSize, which is pretty tedious in most cases.
The correct way of defining the contentSize in iOS is to define AutoLayout constraints in your View. Here is an excellent tutorial on doing just that:
https://www.natashatherobot.com/ios-autolayout-scrollview/

Infinitely scrolling paging scrollview

My UIScrollView has 3 pages, with a separate UIView on each page. This is set up using auto layout. When the app launches, there is one view offscreen to the left, another that is onscreen, and a third offscreen to the right.
When the user pages to either side, there will be one view onscreen, with two views offscreen on the same side. What I want to be able to do is move the view furthest offscreen to the other side of the scrollview, to make it so that the scroll view can page infinitely.
I tried changing the constraints in scrollViewDidEndDragging(_:willDecelerate), but this approach is not working.
Is this effect plausible? If so, how can I achieve this effect?
Edit: One reason the approach before doesn't work is because the user can scroll past the paging without this delegate method being called. I am currently trying to see if the didScroll method will allow for the effect I am going for, but I haven't gotten this to the point where I can test it yet.

Make a UIView receive taps but pass swipes to the underlying view

I've got a UITableView and a big "button" view in front of it. The "button" view, which has transparent areas, should be able to response to a tap. But enabling user interaction for this view blocks any scrolling touches from getting to the table view located under the "button" view.
The upper view is a UIView (not UIButton). Given how the two views work together, the upper view is essentially part of what's going on with the table view and reacts to the table view being scrolled. But scrolling is the main thing and I'd like the user to have the largest scrolling area possible.
How do I best resolve this conflict so that the table view is scrollable as usual?
I guess you could subclass your UIButton and UITableView common superview, and override its hitTest:withEvent: to verify which view is hit, something like if you are in a clear or an opaque part of the button?
As pbush25 is mentionning however, it goes more or less against Apple's recommendation.

Create Shazam Type UI ios Swift

I am trying to recreate a UIView I have seen in multiple apps, mainly Shazam. The top half of the screen has some interactive buttons, and the bottom half looks like a tableView with custom cells. When the bottom half is panned/swiped up, the tableView scrolls over the top half with velocity, much like a scroll view.
I have been researching this and experimenting for a couple days now. I have gotten close, but not quite there.
My last approach was a view that had a tableView inside it. When the view was panned, the view would move to wherever the finger moved it to, but then would not have any velocity afterwards. Also when the tableView was panned/swiped down, it wouldn't move the whole view down.
Before that I tried a scrollView that took up the whole length of the screen. That gave the desired effect, but the button wasn’t tappable, and you could scroll the view in the button area, which is undesired.
Does it utilize ScrollViews or is it using a tableView that acts much like a ScrollView somehow.
Here is the Shazam UI/UX I am looking to recreate:
The top portion has interactive buttons, and doesn’t scroll. The bottom half shows content and when scrolled, covers up the top portion.
Below is what I have tried so far: This one is the panning view, which sort of works, but doesn’t have velocity and the tableView doesn’t scroll the view back down.
Any thoughts on a direction I can take from here is greatly appreciated. I am using Swift.
Cheers
This sort of thing is perhaps best done with a collection view and a custom layout — you can have some items for which you set layout attributes absolute to the view, and others relative to the scroll content offset.
There's a great (if wandering) discussion of this and other techniques in the Advanced User Interfaces with Collection Views talk from WWDC 2014.
This is actually simple than it seems at first. Here's how you can achieve this:
Create a UIViewController (not a UITableViewController).
Add some buttons to the top area of the screen.
Add a table view spanning the entire view controller's view. Make sure the table view is on top of the buttons added in the previous step.
Configure the top cell of the table view to be transparent (by setting its background color to Clear). Set the background color on the table view to Clear as well. This way it won't obscure the elements at the top of the screen, unless the table is scrolled up.
Because your table view is now transparent, you'll need to explicitly set the background color on the table cells other than the top one.
Profit!

Resources