iOS: Programatic ScrollView not scrolling (contentSize set) - ios

I have created a scrollView in code and added several labels to it:
private let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.backgroundColor = .systemTeal
return scrollView
}()
In viewDidLoad:
view.addSubview(scrollView)
[view1, label1, label2, label3, label4, label5].forEach { scrollView.addSubview($0) }
setViewContraints()
Then in viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
scrollView.frame = view.bounds
scrollView.contentSize = CGSize(width: view.width, height: view.height + 300)
}
The first view is anchored to the scrollview safeAreaLayoutGuide topAnchor, then each subsequent label's topAnchor is anchored to the bottom of the previous bottomAnchor. Every view's trailing and leading anchors are anchored to the scrollview's trailing and leading anchors. This is done in setViewConstraints().
view.width and view.height return view.frame.size.width and view.frame.size.height respectively.
All the content appears fine but the scrollView doesn't work. What am I doing wrong?

I realized I needed to set the bottomAnchor of my bottom-most view to the bottom anchor of the scroll view. That did the trick.

Related

Resize constraints for UIView. swift

I have two custom UIViews on the screen. UIViewOne occupies 75% of the screen, and UIViewTwo 25%. I need to click on UIViewTwo, resize it to make the second bigger and smaller first.
I also know that this needs to be done using constraints, but I don't know how. Please tell me how to solve this problem.
For both view1 and view2,
add constraint as equal height to superview
update the height constraint multiplier to 0.75 for view1 and 0.25 for view2.
When you click on view2, similarly update the height constraint multiplier to 0.25 for view1 and 0.75 for view2.
One approach is to add two Height constraints to view1 and change their Priority,
add a Height constraint at 75% (multiplier = 0.75)
set its Priority to 999
add a Height constraint at 25% (multiplier = 0.25)
set its Priority to 998
constraint the Top of view2 to the bottom of view1.
At the start, the 75% Height constraint will have priority over the 25% constraint ... 999 is greater than 998. When you tap view2, change the 75% constraint's Priority to 997. Now 997 is less than 998, so the 25% constraint gets the priority.
Since view2's top is constrained to view1's bottom, view2 will automatically resize.
Here is an example you can run as-is (just assign it to a view controller... no IBOutlet or IBAction connections needed):
class PercentViewController: UIViewController {
let view1: UIView = {
let v = UIView()
v.backgroundColor = .red
return v
}()
let view2: UIView = {
let v = UIView()
v.backgroundColor = .green
return v
}()
var topView75: NSLayoutConstraint!
var topView25: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// we're using auto-layout
view1.translatesAutoresizingMaskIntoConstraints = false
view2.translatesAutoresizingMaskIntoConstraints = false
// add views
view.addSubview(view1)
view.addSubview(view2)
// respect safe area
let g = view.safeAreaLayoutGuide
// create "75% height constraint"
topView75 = view1.heightAnchor.constraint(equalTo: g.heightAnchor, multiplier: 0.75)
// create "25% height constraint"
topView25 = view1.heightAnchor.constraint(equalTo: g.heightAnchor, multiplier: 0.25)
// give 75% constraint higher priority than 25% constraint
topView75.priority = UILayoutPriority(rawValue: 999)
topView25.priority = UILayoutPriority(rawValue: 998)
NSLayoutConstraint.activate([
// view1 constrained Top, Leading, Trailing (to safe-area)
view1.topAnchor.constraint(equalTo: g.topAnchor),
view1.leadingAnchor.constraint(equalTo: g.leadingAnchor),
view1.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// view2 constrained Bottom, Leading, Trailing (to safe-area)
view2.bottomAnchor.constraint(equalTo: g.bottomAnchor),
view2.leadingAnchor.constraint(equalTo: g.leadingAnchor),
view2.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// view2 Top constrained to view1 Bottom
view2.topAnchor.constraint(equalTo: view1.bottomAnchor),
// activate both Height constraints
topView75,
topView25,
])
// create tap gesture recognizers
let tap1 = UITapGestureRecognizer(target: self, action: #selector(view1Tapped(_:)))
let tap2 = UITapGestureRecognizer(target: self, action: #selector(view2Tapped(_:)))
// add to the views
view1.addGestureRecognizer(tap1)
view2.addGestureRecognizer(tap2)
}
#objc func view1Tapped(_ sender: Any) -> Void {
// view1 tapped, so give 75% constraint a higher priority than 25% constraint
topView75.priority = UILayoutPriority(rawValue: 999)
// 0.3-second animation
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
#objc func view2Tapped(_ sender: Any) -> Void {
// view2 tapped, so give 25% constraint a higher priority than 75% constraint
topView75.priority = UILayoutPriority(rawValue: 997)
// 0.3-second animation
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
}

How to increase UIView height which contains UIStackView

I have a custom view which contains a label, label can have multiple line text. So i have added that label inside a UIStackView, now my StackView height is increasing but the custom view height doesn't increases. I haven't added bottom constraint on my StackView. What should I do so that my CustomView height also increases with the StackView.
let myView = Bundle.main.loadNibNamed("TestView", owner: nil, options: nil)![0] as! TestView
myView.lbl.text = "sdvhjvhsdjkvhsjkdvhsjdvhsdjkvhsdjkvhsdjkvhsjdvhsjdvhsjdvhsjdvhsjdvhsjdvhsjdvhsdjvhsdjvhsdjvhsdjvhsdjvhsjdvhsdjvhsdjvhsjdvhsdjvhsjdvhsdjvhsdjvhsdjvhsjdv"
myView.lbl.sizeToFit()
myView.frame = CGRect(x: 10, y: 100, width: UIScreen.main.bounds.width, height: myView.frame.size.height)
myView.setNeedsLayout()
myView.layoutIfNeeded()
self.view.addSubview(myView)
I want to increase my custom view height as per my stackview height.
Please help.
Example of stackView constraints with its superview.
Also superview should not have constraints for its height.
You should set the top and bottom anchors of your custom view to be constrained to the top and bottom anchors of your stackview. As your stackView grows, it will push that bottom margin along. Here's a programmatic example:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
private lazy var stackView = UIStackView()
private lazy var addLabelButton = UIButton(type: .system)
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let stackViewContainer = UIView(frame: view.bounds)
stackViewContainer.backgroundColor = .yellow
stackViewContainer.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackViewContainer)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
addLabelButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(addLabelButton)
stackViewContainer.addSubview(stackView)
NSLayoutConstraint.activate([
// Container constrained to three edges of its superview (fourth edge will grow as the stackview grows
stackViewContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
stackViewContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor),
stackViewContainer.topAnchor.constraint(equalTo: view.topAnchor),
// stackView constraints - stackView is constrained to the
// for corners of its contaier, with margins
{
// Stackview has a height of 0 when no arranged subviews have been added.
let heightConstraint = stackView.heightAnchor.constraint(equalToConstant: 0)
heightConstraint.priority = .defaultLow
return heightConstraint
}(),
stackView.topAnchor.constraint(equalTo: stackViewContainer.topAnchor, constant: 8),
stackView.leadingAnchor.constraint(equalTo: stackViewContainer.leadingAnchor, constant: 8),
stackView.trailingAnchor.constraint(equalTo: stackViewContainer.trailingAnchor, constant: -8),
stackView.bottomAnchor.constraint(equalTo: stackViewContainer.bottomAnchor, constant: -8),
// button constraints
addLabelButton.topAnchor.constraint(equalTo: stackViewContainer.bottomAnchor, constant: 8),
addLabelButton.centerXAnchor.constraint(equalTo: stackViewContainer.centerXAnchor)
])
addLabelButton.setTitle("New Label", for: .normal)
addLabelButton.addTarget(self, action: #selector(addLabel(sender:)), for: .touchUpInside)
self.view = view
}
private(set) var labelCount = 0
#objc func addLabel(sender: AnyObject?) {
let label = UILabel()
label.text = "Label #\(labelCount)"
labelCount += 1
stackView.addArrangedSubview(label)
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
Note that when the UIStackView is empty, its height is not well defined. That is why I set its heightAnchor constraint to 0 with a low priority.
First of all you should add bottom constraint on your UIStackView. This will help auto layout in determining the run time size of UIStackView.
Now create instance of your custom UIView but do not set it's frame and add it to UIStackView. Make sure you Custom UiView has all the constraints set for auto layout to determine it's run time frame.
This will increase height of both UIView and UIStackView based on content of UIView elements.
For more details you can follow my detailed answer on this at https://stackoverflow.com/a/57954517/3339966

Center UIImageView within a UIScrollView with a larger contentSize

I have a full screen scrollView, to which I add an imageView as subview. I want the imageView to be centered and scaled filling the scrollView's size (that is the screen size) at the beginning, but then to allow the user to scroll the image in both directions (vertical and horizontal) with equal offsets at left, right, top and bottom.
I mean: I've set the scroll view's contentSize to be CGSize(width: screenWidth + 200, height: screenHeight + 200), and if I run the app, I see that I am able to scroll those 200 pts of offset only to the right and to the bottom of the image. I'd like the image to be centered in the content size, and to be able to scroll it horizontally to both to the left and to the right with offset 100 pts each side (similar thing with top and bottom when scrolling vertically).
How could I achieve this?
Note: I'm setting all the UI in code, I'm not using storyboards nor xib files
You may find it easier / more intuitive to use constraints and auto-layout rather than screenWidth and screenHeight:
//
// CenteredScrollViewController.swift
// SW4Temp
//
// Created by Don Mag on 4/18/18.
//
import UIKit
class CenteredScrollViewController: UIViewController {
let theScrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor.green
return v
}()
let theImageView: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor.blue
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
// add the scrollView to the main view
view.addSubview(theScrollView)
// add the imageView to the scrollView
theScrollView.addSubview(theImageView)
// pin the scrollView to all four sides
theScrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0.0).isActive = true
theScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0).isActive = true
theScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0).isActive = true
theScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0).isActive = true
// constrain the imageView's width and height to the scrollView's width and height
theImageView.widthAnchor.constraint(equalTo: theScrollView.widthAnchor, multiplier: 1.0).isActive = true
theImageView.heightAnchor.constraint(equalTo: theScrollView.heightAnchor, multiplier: 1.0).isActive = true
// set the imageView's top / bottom / leading / trailing anchors
// this *also* determines the scrollView's contentSize (scrollable area)
// with 100-pt padding on each side
theImageView.topAnchor.constraint(equalTo: theScrollView.topAnchor, constant: 100.0).isActive = true
theImageView.bottomAnchor.constraint(equalTo: theScrollView.bottomAnchor, constant: -100.0).isActive = true
theImageView.leadingAnchor.constraint(equalTo: theScrollView.leadingAnchor, constant: 100.0).isActive = true
theImageView.trailingAnchor.constraint(equalTo: theScrollView.trailingAnchor, constant: -100.0).isActive = true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// set the scrollView's contentOffset (to center the imageView)
theScrollView.contentOffset = CGPoint(x: 100, y: 100)
}
}
You can move only down and right because your current content offset is 0,0 so top left - thus you can move down 200 and right 200.
What you want is to be scrolled 1/2 of vertical padding and 1/2 of horizontal padding, so in your case you would do scrollView.contentOffset = CGPoint(x: 100, y: 100)
Also for everything to work, UIImageView has to be same size as scrollView's contentSize, so bigger than screen size.
Given the comments what I think you want is the image to fill the screen and then user could scroll outside of bounds of the image, then you just need to make UIImageView's size be size of the screen its x and y coordinates to be same as contentOffset of the scrollView so (100, 100).
Here is the video of the sample app doing this:
https://dzwonsemrish7.cloudfront.net/items/2v361r2p0O2j1D3x3W10/Screen%20Recording%202018-04-19%20at%2002.32%20PM.mov
try this in
Swift 4.* or 5.*
let maxScale = self.imageScrollView.maximumZoomScale
let minScale = self.imageScrollView.minimumZoomScale
if let imageSize = imageView.image?.size{
let topOffset: CGFloat = (boundsSize.height - minScale * imageSize.height ) / 2
let leftOffset: CGFloat = (boundsSize.width - minScale * imageSize.width ) / 2
self.imageScrollView.contentInset = UIEdgeInsets(top: topOffset, left: leftOffset, bottom: 0, right: 0)
}

Swift 4 Scrollview Constraints Issue

I added a scrollview to my viewController and anchored it to my view, like this:
class MainContainer: UIViewController {
let mainScrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.backgroundColor = .lightGray
scrollView.isPagingEnabled = true
scrollView.showsHorizontalScrollIndicator = false
scrollView.bounces = false
scrollView.contentInsetAdjustmentBehavior = .never
return scrollView
}()
override func viewDidLoad() {
view.addSubview(mainScrollView)
mainScrollView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
}
}
The above code works great. Constraints work as expected
I then try to append a view to it by adding
mainScrollView.addSubview(cameraView.view)
That is when the constraints act weird on my scrollview. For some reason the width and height of the scrollview is doubled. Here is a screenshot of my view hierarchy to illustrate my issue
In the image I selected the scrollview and right clicked to "Show Constraints" which for some reason are doubled in width and height. Before adding the view controller the constraints where fine. The added view controller appears fine but the constraints on the scrollview are messed.
Make sure that cameraView.view's top , bottom , leading and trailing constraints are hooked to the scrollview (the superview) then give a height and width to cameraView.view , also don't forget to make translateAutoresizing.... equal to false for the scrollview . . .

Horizontal UIStackView inside UIScrollView not scrolling

I have a horizontal UIStackView with 3 buttons inside UIScrollView. I'd like the stack to scroll horizontally when the user changes the font size (via Accessibility).
Right now, when the user increases the font size, one of the buttons gets squashed.
Here are my constraints:
It seems like the Greater Than or Equal constraint between the scroll view and the button on the right was the problem. I changed it to Equal and it worked.
Make sure the UIScrollView's content size is set up correctly. Here are some useful parameters.
scrollVIew.contentInset
scrollVIew.scrollIndicatorInsets
scrollView.contentSize
When the content view is taller than the scroll view, the scroll view enables vertical scrolling. When the content view is wider than the scroll view, the scroll view enables horizontal scrolling. Otherwise, scrolling is disabled by default. You must set your content view size dynamically so when they change the font, the content view gets wider than the scroll view width. You could wrap your stack view in another UIViewController and treat it as a content view.
Try this working fine my side and scrolling well.
func createHorizontalStackViewWithScroll() {
self.view.addSubview(stackScrollView)
stackScrollView.translatesAutoresizingMaskIntoConstraints = false
stackScrollView.heightAnchor.constraint(equalToConstant: 85).isActive = true
stackScrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
stackScrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
stackScrollView.bottomAnchor.constraint(equalTo: visualEffectViews.topAnchor).isActive = true
stackScrollView.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: stackScrollView.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: stackScrollView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: stackScrollView.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: stackScrollView.bottomAnchor).isActive = true
stackView.heightAnchor.constraint(equalTo: stackScrollView.heightAnchor).isActive = true
stackView.distribution = .equalSpacing
stackView.spacing = 5
stackView.axis = .horizontal
stackView.alignment = .fill
for i in 0 ..< images.count {
let photoView = UIButton.init(frame: CGRect(x: 0, y: 0, width: 85, height: 85))
// set button image
photoView.translatesAutoresizingMaskIntoConstraints = false
photoView.heightAnchor.constraint(equalToConstant: photoView.frame.height).isActive = true
photoView.widthAnchor.constraint(equalToConstant: photoView.frame.width).isActive = true
stackView.addArrangedSubview(photoView)
}
stackView.setNeedsLayout()
}

Resources