UIScrollview not working with Storyboard - swift

Hello I'm trying to setup a scrollview in the storyboard and followed the following steps to do it:
add a scrollview to the root view.
pin zero spaces to all edges of super view.
add a UIView (contentView) to the above scrollview.
pin zero spaces to all edges of the scrollview add some widgets to
contentView and change the height of the contentView to 2000.
I saw this solution is working on many tutorials I saw but not its not working on me. What am I doing wrong? I use swift.
Can someone explain step by step how do to set up a scrollview in the storyboard?
My constrains:

OK, let's imagine a the following view hierarchy (note, looking at constraints in Interface Builder are easier if you give those views unique names in the "Document" section of the "Identity Inspector"):
To set that up, you'd add the following constraints in IB (I'm justing going to write it in VFL, because it's a very concise way of showing the constraints):
Obviously, define scroll view relative to its superview (the main view, in this example):
H:|[scrollView]|
V:|[scrollView]|
Define, contentView such that
It's width is the same as the main view (==view), and
The scroll view's contentSize will change to fit the size of the contentView. Per TN2154, the constraints between a scrollview and its subviews defines the contentSize of the scroll view, not the relative size of the subviews.
Thus:
H:|[contentView(==view)]|
V:|[contentView]|
Define the layout for three labels such that they're offset within the contentView:
H:|-[label1]-|
H:|-[label2]-|
H:|-[label3]-|
Rather than hardcoding the height of the contentView (and thus the contentSize of the scroll view, instead just define the label's relationship to the vertical height of the contentView, which (because of step 2, above), adjusts the vertical height of the contentSize of the scroll view:
V:|-[label1]-[label2]-[label3]-|
This is all you need to do. I didn't hardcode any widths (the main view has a width automatically, and both the scroll view and, more importantly, the contentView define their widths related to that. But the label widths are inset from the contentView, and the contentView height (and thus the scroll view's contentSize) is inferred from the intrinsic height of the three labels.
The end result is constraints in IB that look like:
FYI, if you want to do some diagnostics, you can click on the view debug button while the app is running on the simulator:
You can see the view (and optionally the constraints) and make sure everything looks ok:
You can also look at the _autolayoutTrace via the (lldb) prompt:
(lldb) po [[UIWindow keyWindow] _autolayoutTrace]
UIWindow:0x7fbbb3617910
| •UIView:0x7fbbb349a840
| | *UIScrollView:0x7fbbb3491c80
| | | *UIView:0x7fbbb348e180
| | | | *UILabel:0x7fbbb348e450'Label'
| | | | *UILabel:0x7fbbb3490670'Label'
| | | | *UILabel:0x7fbbb3490a70'Label'
| | | UIImageView:0x7fbbb34a3eb0
| | | UIImageView:0x7fbbb34a3800
| | *_UILayoutGuide:0x7fbbb349a970
| | *_UILayoutGuide:0x7fbbb349b460
This confirms that there are no conflicting layouts and that there are no ambiguous layouts.

Related

Change height of one of multiple fields in stack view

In my code I have a stack view that initially has 1 element PhoneNumberField. Another PhoneNumberFieldscan be added dynamically in the runtime:
#IBAction func addAlternatePhoneNumberAction(_ sender: UIButton) {
let alternateNumberView = PhoneNumberField()
...
phoneNumberStackView.addArrangedSubview(alternateNumberView)
}
This is what xib for PhoneNumberFieldlooks like:
The problem is that I would like to be able to dynamically hide the 'Name for other phone' field based on the content of 'Mobile combobox'. When I set the 'isHidden' parameter everything works as expected, the only problem is that the PhoneNumberField height stays the same. I would like it to shrink when the 'Name for other phone' field is hidden.
I tried doing it using the outlet for height constant for otherNumberNameField in the PhoneNumberField.swift file but the problem is that in that case all of the PhoneNumberFields in the stack view have the size of the first field.
What would be the correct solution for this?
edit: In addition to the answer below: I had to set the distribution for the phoneNumberStackView to equal spacing. Worked like a charm.
First, create StackView.
Don't set its height constraint, just set top, leading, trailing and bottom constraints.
Bottom constraint set equal to Error label top constraint.
Then set its distribution to Fill Equally.
Now put first two Views into one view and put this view together with OtherNumberField view to this StackView.
So now your hierarchy should look like this:
Now when you hide one view from StackView, StackView will be smaller because you didn't set its height.

Resizing large UITable grinds app to a halt

I have a UIViewController that contains a UIScrollView which itself contains both a UITableView and another UIScrollView. Inside the nested UIScrollView is another UITableView.
When the in ViewDidAppear of the UIViewController I calculate how big the tables should be (much bigger than the screen) and set their sizes and then set the content size of the UIScrollView to match the tables they contain (the UIScrollView will provide the scrolling rather than the table itself). This all works fine in the simulator, but on an actual device, it grinds to a halt, pegging the processor at ~100% for tens of seconds. This is obviously unacceptable. Does anybody have any idea why? Or how I can work around it?
The code looks something like this:
OuterScrollView.ContentSize = new SizeF (View.Frame.Width, tableHeight);
InnerScrollView.ContentSize = new SizeF (InnerTable.Frame.Width, tableHeight);
InnerScrollView.Frame = new RectangleF(InnerScrollView.Frame.Location,
new SizeF(InnerScrollView.Frame.Width, tableHeight));
// So far so good
OuterTable.Frame = new RectangleF(OuterTable.Frame.Location,
new SizeF(OuterTable.Frame.Width, tableHeight)); // this slows everything down!!
InnerTable.Frame = new RectangleF(InnerTable.Frame.Location,
new SizeF(InnerTable.Frame.Width, tableHeight)); // and so does this
Removing both of the table .Frame setting statements and everything works quickly enough, but with them in it's very slow. And the slowness doesn't come directly here, but somewhere after the call to the base ViewDidAppear.
Update: I had an epiphany and thought if resizing the table is the problem, just make the tables big enough that they don't need resizing. In the setup I have, the scrolling is being handled by the scroll view not the table itself, so I could just set the table as really big and let the scroll views ContentSize take care of clipping the blank part of the table. This works, in the sense that it displays how I want it to, but it's actually even slower! So my conclusion is that it's not the table resize that is the problem, but rather the presence of really long (in this case I set the height to 4,000 - the resize was setting it to 2,354) tables.
Background: To add a little more to what I'm trying to do. Since Apple, in their wisdom, decided that nobody needs a grid like control, I'm trying to set up a situation where I have a grid-like view where the left most columns stay in place, but you can horizontally scroll the right-most columns, and, when you scroll vertically, everything will stay in sync. After some searching, I came across a solution (sorry, can't remember exactly where), which with a little tweaking works (in the simulator). Basically, you embed tables in a scroll view so that the scroll view can handle the scrolling. The layout looks something like this:
+-------------------------Outer Scroll-------------------------+
| +---------------Inner Scroll---------------+|
|+--Fixed Table--+ |+---------Scrolling Table----------------+||
|| | || |||
|| | || |||
|| | || |||
|| | || |||
|| | || |||
|| | || |||
|+---------------+ |+----------------------------------------+||
| +------------------------------------------+|
+--------------------------------------------------------------+
The fixed table has a custom cell with a couple of columns while the scrolling table has another custom cell (wider than the screen) with the rest of the columns. You can scroll the scrolling table horizontally (thanks to the inner scroll view) and you can scroll everything vertically thanks to the outer scroll view.
Another Update: So it seems the problem is with the table not reusing cells when you set it large. I guess the cell reuse logic only extends to determining if the cell is within the frame of the table and not if that part of the table is actually visible. So with 50 items, instead of displaying 6-7 and then re-using those cells, it creates all 50 regardless. So I abandoned my earlier attempt and tried to synchronize the scrolling between two tables like this:
OuterTable.Scrolled += (sender, arg) =>
{
InnerTable.ContentOffset = new PointF(InnerTable.ContentOffset.X,
OuterTable.ContentOffset.Y);
};
InnerTable.Scrolled += (sender, arg) =>
{
OuterTable.ContentOffset = new PointF(0, InnerTable.ContentOffset.Y);
};
This almost works, except iOS doesn't like horizontal scrolling for tables, so the inner table still needs to be wrapped in a scroll view (set with the ContentSize to cover the width I need to scroll). At this point it almost works. It will usually keep seemlessly in sync, but with a bit of messing around you can get, for example, the inner table to scroll diagonally (despite setting direction lock on everybody) and in some cases it's possible to get them out-of-sync, which looks really stupid. So it doesn't quite work as well visually, but at least it doesn't hang up the UI thread as badly.
Have you tried doing this with autolayout? There you are not responsible for when the views actually get resized, just setup the constraints in viewDidLoad and let the rest be taken care of by the system.
But in the terms of manual layout - I don't see why this would actually happen. When you profiled it with Instruments, what was the slow portion in Time Profiler?
From the documentation:
Important: You should not embed UIWebView or UITableView objects in
UIScrollView objects. If you do so, unexpected behavior can result
because touch events for the two objects can be mixed up and wrongly
handled.
Your interface might appear slow because touch events are getting handled by the wrong objects. This can make scroll views appear to jump around, act laggy, or not respond at all.
Can you describe the behavior you're going for? Can you achieve what you want using only a UITableView with a UIScrollView in it, or perhaps a UICollectionView?
It appears that the only way for your proposed implementation to work is to load ALL the UI at initialization, which eliminates the performance benefit of using a UITableView at all. It will be considerably more efficient (from a memory perspective) for you to have a top level UITableView that scrolls normally, and contain a horizontally scrolling UIScrollview within each cell.
+-------------------------Table View---------------------------+
|+-----------------------Table View Cell---------------------+|
||+--Fixed Section--+ +---------Synched Scroll View---------+||
||| | | |||
||| | | |||
||+-----------------+ +-------------------------------------+||
|+-----------------------------------------------------------+|
|+-----------------------Table View Cell---------------------+|
||+--Fixed Section--+ +---------Synched Scroll View---------+||
||| | | |||
||| | | |||
||+-----------------+ +-------------------------------------+||
|+-----------------------------------------------------------+|
|+-----------------------Table View Cell---------------------+|
||+--Fixed Section--+ +---------Synched Scroll View---------+||
||| | | |||
||| | | |||
||+-----------------+ +-------------------------------------+||
|+-----------------------------------------------------------+|
+--------------------------------------------------------------+
The important step is synching the horizontal scroll views, which you can do by sending NSNotifications or custom delegate messages on -scrollViewDidScroll:
It may feel wasteful to be creating 10+ scrollviews within table view cells, but in practice it ends up being fairly efficient.

How to achieve an advanced table view header

I have a grouped UITableView. Now unlike the default table view header my header should be next to the individual cells:
Standard:
[Header Cell]
[Item 1]
[Item 2]
What I need
+-------+----------------+
| | Item 1a |
| h1 +----------------+
| | Item 1b |
+ - - - +----------------+
| Item 1c |
+------------------------+
| | Item 2a |
| h2 +----------------+
| | Item 2b |
+------------------------+
| | Item 3a |
| h3 +----------------+
| | -placeholder- |
+-------+----------------+
Some remarks:
Group H1 has three items
Group H2 has two items
Group H3 has just one item (I have to insert a placeholder cell so that the header cell can have the full height of two item-cells
When the user scrolls the list then the header should be pushed upwards that the two header-cells don't overlap.
The challenge here is the height of the header cell:
If I set the height to 0 and uncheck clip subviews then that header cell is shown but does not get pushed away at the correct position.
If I set the height of the header cell to the visual height then there is an empty space of that height across the whole width of the table which I don't want...
Update 1: I just realized that apple uses such a list for the search (grouped by messages, mails, contacts, calenders etc.). So my question basically is how can I tweak UITableView in order to behave like the grouped search results... :-)
(source: mshcdn.com)
Update: I created a project on github, which does exactly this. I have extracted all the relevant code for the behaviour into two classes (BBFloatingHeaderViewController & BBFloatingHeaderCell). There is also an example project. I hope this is useful for others :-)
Here's the project: besi/FloatingTableViewHeader
This is an excerpt from the readme:
Floating UITableView headers
These classes aim to copy the behaviour found in iOS built-in Spotlight search, where the search results are grouped by category and the icon of the respective category floats on the left side of the search results.
Setup
Check out the example project so see how to setup the classes.
Add the .m + .h files of BBFloatingHeaderViewController and BBFloatingHeaderCell to your project.
Create a TableView and set the Class of the ViewController to your subclass of BBFloatingHeaderViewController
Create the header cell in IB and set its class to your subclass of BBFloatingHeaderCell
Make sure that your floating header view is the topmost view in the BBFloatingHeaderCell's view hierarchy.
The result
Floating Headers http://i.minus.com/jyea3I5qbUdoQ.png

steps for creating UIScrollView with Interface Builder

I'm in the midst of trying to use a UIScrollView and there appears to be some fundamental thing that I'm just not understanding.
Let's say I want to use a UIScrollView in my iphone app. I have a View filled with buttons that is 320x700. Obviously, this is too big for the iPhone which is 320x480. So I know I have to use a UIScrollView. However, is this the order that I should be creating the objects
Create a UIScrollView that is 320x700 as the dimensions in "View"
Place all my buttons, etc, on this scroll view
In the viewDidLoad set the contentSize to 320x700
Set the delegate of the UIScrollView to the File Owner, and the view of the FileOwner to the UIScrollView
Reset the size of the View back to 320x480.
Is this right?
This works, but it doesn't make sense to me. I get that the View is supposed to be the canvas, where I add all the UI elements. I want the "canvas" of the iPhone app to be 320x700, and I want to be able to put my buttons, etc on this 320x700 canvas. But if I don't change the size of the UIScrollView back to 320x480, it won't scroll, because I need to set the content size of the UIScrollView larger than its size.
But if I set the size of the UIScrollView to 320x480, then I don't see the screen and the buttons between 480 and 700 in Interface Builder! So it seems like I'm supposed to make all my edits and add all my UI elements to the UIScrollView, and then set it back to the 320x480!
Is there some other way to do this that makes more sense? What am I missing in my understanding of how this should work?
UPDATE
I have posted another solution here which I think is simpler and better.
ORIGINAL
Here's another way to do this that you might like better:
Set the File's Owner placeholder's custom class to your view controller subclass.
Create the UIScrollView as a top-level object in your nib. Set its size to the screen size (320x460) or just turn on a status bar under "Simulated Metrics".
Connect the scroll view's delegate outlet to File's Owner.
Set the File's Owner's view outlet to the scroll view.
Create a UIView as another top-level object in your nib. This will be your content view.
Set the content view's size to 320x700.
Create a strong (or retain, if not using ARC) outlet named contentView in your view controller (File's Owner) and connect it to the content view.
Put your buttons in the content view.
In your view controller's viewDidLoad, do this:
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.contentView];
((UIScrollView *)self.view).contentSize = self.contentView.frame.size;
}
In your view controller's viewDidUnload, do this:
- (void)viewDidUnload {
self.contentView = nil;
[super viewDidUnload];
}
Full size
You are right, the View is the canvas where you add all the UI elements.
Interface builder is kind of weird at first but you will get used to it, that is just the way it works.
You are getting stuck on the fact that you have to resize the ScrollView. You should think of it like this:
The ScrollView has a frame size and a content size. The way it's built is that if the content size is larger than the frame size then it will scroll. You have to make the frame as big as you need to in the interface builder so you can position the elements that go inside. When you run the application you should resize the frame of the scroll to fit inside the iPhone's screen resolution. It doesn't make sense for your controls to have a frame bigger than the screen.
--------------------------------------------
| | | |
| | | |
| | | |
| | | |
| | |<------------------ iPhone Screen frame
| | | |
| | | |
| | | |
| | | |<------- ScrollView Content size
| | | |
| | | |
| | |<----------------- ScrollView Frame Size
| | | |
| | | |
| | | |
| | | |
| | | |
| ------------------------ |
| |
| |
| |
--------------------------------------------
I hope this representation will make it clearer how things should suppose to work.To put it some other way, the scroll frame is the hole trough you can see the content, if the whole is as big as the content then you have no need to scroll cos you can see it all.
I would suggest also trying to write the components in code without using IB to get a better understanding.

How to reposition UILabels and UIImageViews whose dimensions change dynamically?

I have a UITableView with many rows that push a DetailViewController depending on the selection. This detail view contains a UIImageView, and several UILabels (let's say book description, book comments etc). In viewWillAppear I assign the proper content to these outlets, in order to display content for the selected row. My problem is that the text might be one sentence, or a couple of paragraphs. So, if I have one outlet for book description and one other for book comments, how am I supposed to position these components properly, so that their horizontal distance is always the same? I thought of creating a UIWebView and load there dynamically generated HTML, but there has to be a cleaner way, right??
Here is a visual something if that helps :)
------------------------------
| Description |
| [multiple lines go here] |
| |
| [Image] |
| |
| Comments |
| [multiple lines go here] |
------------------------------
When you layout your UITableView, If you're using UILabels for the description and comments you can figure out the size with something like this:
CGSize labelSize = [nameLabel.text sizeWithFont: nameLabel.font
constrainedToSize: CGSizeMake( actualWidth, MAXFLOAT )
lineBreakMode: UILineBreakModeWordWrap];
Once you know the height for Description lets say, you can position the UIImageView appropriately using CGRect, etc, etc. Then calculate the height of the UITableView row with something like this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
Create a custom view class and perform layout in -layoutSubviews using -sizeThatFits: and -sizeToFit.
Layout code does not belong in the view controller.