Adding a vertical UIScrollView programmatically - ios

I'm not making progress on this problem for a while now.
I'm trying to add a vertical scroll view to my app with a contentView (UIView). I added the contentView as a subview of the scrollView & added the scrollView as a subview of the main view. After that, I wasn't able to scroll and buttons + the searchbar weren't working anymore. It just felt like the app was frozen.
What I've done so far is the following:
Creating the scroll- and contentView directly in the class
let scrollView:UIScrollView = UIScrollView()
let contentView:UIView = UIView()
Setting up the scrollView
func setupScrollView() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
contentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.contentSize = CGSize(width: self.view.frame.width, height: self.view.frame.height)
// Add scroll view to main view
self.view.addSubview(scrollView)
// Add content to scrollview
scrollView.addSubview(contentView)
// Constraints
scrollView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
scrollView.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
contentView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
}
Adding several elements as subview to the contentView
// Add search bar to scroll content view
contentView.addSubview(searchBar)
The full code is available on my github repo
I looked through several similar problems and tutorials but somehow can't figure out what's wrong here.

Related

How to set up UIScrollView programatically to scroll horizontally using autoLayout

I am trying to acheve a behavior where i have a form in a table view with a minimum width. So basically, if the screen i wide, the view only scrolls vertically but when the screen is narrow, the form shrinks to its minimum width and then starts to scroll horizontally as to not loose content. So, I need to use autoLayout to set up these constraints and I want to do it programatically (because of frameworks I'm using).
The problem is, I can't get my view to scroll horizontally AT ALL. I have read all I could find about it, and tried everything I could think of, nothing works. Right now I try to just add a large picture to my view and make it scroll both directions, but I can't make it work.
This is my hierarchy:
>UIView
>>UIScrollView
>>>ContentView (UIView)
>>>>ImageView
And this is my code:
override func viewDidLoad() {
if scrollView == nil {
scrollView = UIScrollView(frame: view.bounds)
scrollView.backgroundColor = .groupTableViewBackground
scrollView.isDirectionalLockEnabled = true
scrollView.showsHorizontalScrollIndicator = true
}
if scrollView.superview == nil {
view.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
let contentView = UIView(frame: view.bounds)
scrollView.addSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
let a = contentView.heightAnchor.constraint(equalTo: scrollView.heightAnchor)
let b = contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
a.priority = .defaultLow
b.priority = .defaultLow
a.isActive = true
b.isActive = true
let image = MyImage.icon(named: "myImage", of: CGSize(width: 1000, height: 1000))
let imageView = UIImageView(image: image)
imageView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(imageView)
imageView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
}
super.viewDidLoad()
}
When I debug the view hirearchy, I can see that the size of the image is still correct (1000, 1000) but the content size of the scrollView isn't updated and the picture is truncated, with no scroll.
<UIScrollView: 0x11ead5c00; frame = (0 0; 834 1112); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x1c8853800>; layer = <CALayer: 0x1c8c32240>; contentOffset: {0, -64}; contentSize: {834, 1112}; adjustedContentInset: {64, 0, 0, 0}>
<UIImageView: 0x11a89b000; frame = (0 0; 1000 1000); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x1c8231c80>>
I found and looked at this example to setup a scrolling view programatically https://github.com/zaxonus/AutoLayScroll. It scrolls vertically, but for this either, I can't for my life set it up to scroll horizontally.
Please help, what am I overlooking?
To make your example scrollView content size be 1000x1000 just add these constraints to imageView:
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
Without them your contentView doesn't know it should wrap imageView so its' size is calculated according to low priority constraints a and b
Thanks to Anton, I got it working! I just thougt i'd share my final solution if anyone else is trying to do what I wanted.
With the below code you can add horizontal scroll functionality to the forms of the Eureka frameworks FormViewController (https://github.com/xmartlabs/Eureka).
import UIKit
import Eureka
class MyScrollableViewController: FormViewController {
var scrollView: UIScrollView!
var minimumWidth: CGFloat = 500.0
override func viewDidLoad() {
if tableView == nil {
tableView = UITableView(frame: view.bounds, style: .grouped)
if #available(iOS 9.0, *) {
tableView.cellLayoutMarginsFollowReadableWidth = false
}
}
if scrollView == nil {
scrollView = UIScrollView(frame: view.bounds)
scrollView.backgroundColor = .groupTableViewBackground
scrollView.isDirectionalLockEnabled = true
scrollView.showsHorizontalScrollIndicator = true
}
if scrollView.superview == nil && tableView.superview == nil {
view.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
let contentView = UIView(frame: view.bounds)
scrollView.addSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
let a = contentView.heightAnchor.constraint(equalTo: scrollView.heightAnchor)
let b = contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
a.priority = .defaultLow
b.priority = .defaultLow
a.isActive = true
b.isActive = true
contentView.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
tableView.widthAnchor.constraint(greaterThanOrEqualToConstant: minimumWidth).isActive = true
}
// Has to be called after setting up the tableView since we override the Eureka default setup
super.viewDidLoad()
// Setup your Eureka form as usual
form +++ Section()
<<< LabelRow { row in
row.title = "My first row"
}
}
}

UIScrollView with AutoLayouts programmatically

I have a UIScrollView which i want to scroll vertically. I am creating UIScrollView programmatically and facing problems with Auto layouts.
I have added single child into a UIScrollView and rest of the controls have been added on that child view.
Following Code will create UIScrollView and contentView of Scroll View
lazy var scrollView : UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.keyboardDismissMode = .onDrag
// scrollView.backgroundColor = .red
return scrollView
}()
let contentView: UIView = {
let contentV = UIView()
contentV.translatesAutoresizingMaskIntoConstraints = false
contentV.backgroundColor = UIColor.yellow
return contentV
}()
Following Code will add UIScrollView inside main view and contentView inside UIScrollView
view.addSubview(scrollView)
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 8).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -8).isActive = true
//Content View
scrollView.addSubview(contentView)
contentView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
after doing this I am trying to add other controls on the contentView But my view is not showing properly, See the following image I have googled many tutorials for adding scroll view programmatically but unable to do it so.

Swift: I'm not able to make autolayout constraints work for my UIScrollView

I'm trying to make a simple layout that should scroll vertically when views are too many to fit on the screen.
So this is what I have so far:
I created a scrollview and a container view like that
let mainScrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.isUserInteractionEnabled = true
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
let containerView: UIView = {
let view = UIView()
view.backgroundColor = .lightGray
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
Then I added the scrollview to the main view, added the container view to the scrollview and a couple of labels to the container view
view.addSubview(mainScrollView)
mainScrollView.addSubview(containerView)
containerView.addSubview(firstLabel)
containerView.addSubview(secondLabel)
I pinned the scrollview to the main view, and the container view to the scrollview. After that I started adding the labels inside the container view like this
mainScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
mainScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
mainScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
mainScrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
containerView.topAnchor.constraint(equalTo: mainScrollView.layoutMarginsGuide.topAnchor).isActive = true
containerView.bottomAnchor.constraint(equalTo: mainScrollView.layoutMarginsGuide.bottomAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
firstLabel.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
firstLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 30).isActive = true
firstLabel.widthAnchor.constraint(equalToConstant: 50).isActive = true
firstLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
secondLabel.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
secondLabel.topAnchor.constraint(equalTo: firstLabel.bottomAnchor, constant: 750).isActive = true
secondLabel.widthAnchor.constraint(equalToConstant: 50).isActive = true
secondLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
The problem is that for some reason the scrollview is not able to calculate it's height and it doesn't scroll vertically. The second label remains invisible because it's too "low" on the screen.
I tried setting the bottom constraint of the container view to the bottom of the second label but I'm going nowhere honestly.
What am I missing? How can I set constraints to make the scrollview scroll with autolayout (without setting a specific height of the container view)?
Scrollview scrolls in either direction. So any view added to it would need some additional constraints to let the Scrollview calculate its content size.
You have pinned the container view to scroll view. If you are looking at a vertically scrolling view like a table view then you need to also set the width of the scroll to the container view so that it can calculate the width.
Next for height, its automatically calculated based on the UI elements added in the container view. Ensure that all of the labels have leading, trailing to the container and vertical spacing to each other and top of the container view. This will let the scroll view know the height needed.
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.translatesAutoresizingMaskIntoConstraints = false
self.label1.translatesAutoresizingMaskIntoConstraints = false
self.label2.translatesAutoresizingMaskIntoConstraints = false
self.label3.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(self.label1)
self.contentView.addSubview(self.label2)
self.contentView.addSubview(self.label3)
self.scrollView.addSubview(self.contentView)
self.view.addSubview(self.scrollView)
NSLayoutConstraint.activate([
self.scrollView.topAnchor.constraint(equalTo: self.view.topAnchor),
self.scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
self.scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
self.scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor)
])
NSLayoutConstraint.activate([
self.contentView.topAnchor.constraint(equalTo: self.scrollView.topAnchor),
self.contentView.trailingAnchor.constraint(equalTo: self.scrollView.trailingAnchor),
self.contentView.bottomAnchor.constraint(equalTo: self.scrollView.bottomAnchor),
self.contentView.leadingAnchor.constraint(equalTo: self.scrollView.leadingAnchor),
self.contentView.widthAnchor.constraint(equalTo: self.scrollView.widthAnchor)
])
NSLayoutConstraint.activate([
self.label1.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor),
self.label2.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor),
self.label3.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor),
self.label1.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor),
self.label2.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor),
self.label3.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor)
])
NSLayoutConstraint.activate([
self.label1.topAnchor.constraint(equalTo: self.contentView.topAnchor),
self.label2.topAnchor.constraint(equalTo: self.label1.bottomAnchor),
self.label3.topAnchor.constraint(equalTo: self.label2.bottomAnchor),
self.label3.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor)
])
And when run, it won't complain about any constraint issues either. Same can be done for horizontal scroll.
Thank you very much #GoodSp33d, you pointed me to the right direction.
I added the width constraint as you said like that:
containerView.widthAnchor.constraint(equalTo: mainScrollView.widthAnchor).isActive = true
And the key point was to update the bottom constraint after all:
containerView.bottomAnchor.constraint(equalTo: secondLabel.bottomAnchor).isActive = true
Here is the full working code:
mainScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
mainScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
mainScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
mainScrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
containerView.topAnchor.constraint(equalTo: mainScrollView.topAnchor).isActive = true
containerView.trailingAnchor.constraint(equalTo: mainScrollView.trailingAnchor).isActive = true
containerView.bottomAnchor.constraint(equalTo: mainScrollView.bottomAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo: mainScrollView.leadingAnchor).isActive = true
containerView.widthAnchor.constraint(equalTo: mainScrollView.widthAnchor).isActive = true
firstLabel.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
firstLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 30).isActive = true
firstLabel.widthAnchor.constraint(equalToConstant: 50).isActive = true
firstLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
secondLabel.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
secondLabel.topAnchor.constraint(equalTo: firstLabel.bottomAnchor, constant: 550).isActive = true
secondLabel.widthAnchor.constraint(equalToConstant: 50).isActive = true
secondLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
containerView.bottomAnchor.constraint(equalTo: secondLabel.bottomAnchor).isActive = true
Thanks for the help.

iOS 9 Programmatic Constraints to a Nav Bar?

I am trying to programmatically set my constraints to the top of my UIViewController's Nav Bar, but I can't see to get it to work properly. Here is my code:
func setup() {
let containerView = UIView()
containerView.backgroundColor = .red
containerView.translatesAutoresizingMaskIntoConstraints = false
let payeeNameTextField = UITextField()
payeeNameTextField.backgroundColor = .green
payeeNameTextField.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(containerView)
view.addSubview(payeeNameTextField)
//iOS 9 Constraints
//x,y,w,h
// Constraint To Nav Bar
containerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
containerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
containerView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
containerView.heightAnchor.constraint(equalToConstant: 20).isActive = true
// Constraint to Bottom Anchor
payeeNameTextField.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
payeeNameTextField.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
payeeNameTextField.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
payeeNameTextField.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
I know setting it topAnchor is wrong, but I have tried to mess around with this and just cant seem to get the red background to show up.
Thanks in advance for any input!!

UIScrollView nested subviews not displayed using Auto Layout

I am trying to generate views displayed in a UIScrollView based on structured (XML) data at runtime. I generate all the views I need and add them to my scrollview afterwards, using Auto Layout to let the scrollview calculate its contentSize. Everything worked fine as long as I was only using UILabels and a custom UIImageView subclass, but now I want to add a nested view containing another view and a label to represent text with a vertical line on the left side.
The general layout code I'm using to add all generated views to the scrollview is as follows:
var previousAnchor = scrollView.topAnchor
views.forEach { (view) in
scrollView.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.topAnchor.constraint(equalTo: previousAnchor,
constant: UI.defaultPadding).isActive = true
view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor,
constant: UI.smallPadding).isActive = true
view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor,
constant: -UI.smallPadding).isActive = true
previousAnchor = view.bottomAnchor
}
scrollView.bottomAnchor.constraint(equalTo: previousAnchor,
constant: UI.defaultPadding).isActive = true
And the layout code for my nested subview looks like this (it's a UIView subclass):
addSubview(label)
addSubview(line)
line.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
line.topAnchor.constraint(equalTo: topAnchor).isActive = true
line.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
line.widthAnchor.constraint(equalToConstant: 2).isActive = true
line.trailingAnchor.constraint(equalTo: label.leadingAnchor).isActive = true
line.backgroundColor = UIColor.green
label.topAnchor.constraint(equalTo: topAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
label.numberOfLines = 0
label.text = "Here's to the crazy ones. The misfits. The rebels. The troublemakers."
If I use this code, I cannot see this view in my scrollview at all and the scrollview has a warning of ambiguous content size attached to it in the Debug View Hierarchy view. This is somewhat irritating, as using labels without being nested in a view like this does work like a charm. So I started playing around and what really puzzles me is that if I use the following code I can see the view itself (as I gave it a gray background color), but still can't see line and/or label, as both seem have a frame of (0,0,0,0) upon inspection:
addSubview(label)
addSubview(line)
line.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
line.topAnchor.constraint(equalTo: topAnchor, constant: 10).isActive = true
line.widthAnchor.constraint(equalToConstant: 20).isActive = true
line.heightAnchor.constraint(equalToConstant: 20).isActive = true
line.trailingAnchor.constraint(equalTo: label.leadingAnchor).isActive = true
line.backgroundColor = UIColor.green
label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
heightAnchor.constraint(equalToConstant: 100).isActive = true
heightAnchor.constraint(equalTo: label.heightAnchor).isActive = true
label.numberOfLines = 0
label.text = "Here's to the crazy ones. The misfits. The rebels. The troublemakers."
What am I missing? I also tried putting all my views into a container view instead of using them as subviews of the scrollview directly, but this did not help with my nested view and brought up other problems.

Resources