Dynamic Constraint for UIView & UICollection - ios

On the top of my screen will show one of two UIViews.
One is the minimized version and the other is the maximized verison.
The minimized view is 30 in height while the maximized version is 250.
Underneath this there is a UICollectionView.
I want it so that when the minimized version of the UIView is showing, the UICollectionView's topAnchor will be connected to the UIViews bottomAnchor.
When I click on each of the UIViews they will become hidden and make the other one visible.
Here are some screenshots to help visualize:
Default
Minimized
Attempt to maximize
So when I show the maximized UIView I want the UICollectionViews topAnchor to be connected to that ones bottomAnchor and so forth.
Currently everything is working except the proper resizing of the UICollectionView.
It will resize up when I minimize, but will not resize down when I maximize.
My viewDidLoad calls both of these functions:
private func configureMaxView() {
view.addSubview(maxView)
maxView.layer.cornerRadius = 18
maxView.backgroundColor = .secondarySystemBackground
maxView.translatesAutoresizingMaskIntoConstraints = false
maxView.isHidden = isMaxViewHidden
let gesture = UITapGestureRecognizer(target: self, action: #selector (self.minimizeAction (_:)))
self.maxView.addGestureRecognizer(gesture)
let padding: CGFloat = 20
NSLayoutConstraint.activate([
maxView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
maxView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding),
maxView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -padding),
maxView.heightAnchor.constraint(equalToConstant: 250),
])
}
private func configureMinView() {
view.addSubview(minView)
minView.layer.cornerRadius = 9
minView.backgroundColor = .secondarySystemBackground
minView.translatesAutoresizingMaskIntoConstraints = false
minView.isHidden = !isMaxViewHidden
let gesture = UITapGestureRecognizer(target: self, action: #selector (self.expandAction (_:)))
self.minView.addGestureRecognizer(gesture)
let padding: CGFloat = 20
NSLayoutConstraint.activate([
minView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
minView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding),
minView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -padding),
minView.heightAnchor.constraint(equalToConstant: 30),
])
}
Here are the functions that are called when you click on one of the UIViews:
#objc func minimizeAction(_ sender:UITapGestureRecognizer){
minView.isHidden = false
maxView.isHidden = true
// without this call the hiding and unhiding works fine - But the collectionview won't move
resizeCollectionView()
isMaxViewHidden = !isMaxViewHidden
}
#objc func expandAction(_ sender:UITapGestureRecognizer){
minView.isHidden = true
maxView.isHidden = false
// without this call the hiding and unhiding works fine - But the collectionview won't move
resizeCollectionView()
isMaxViewHidden = !isMaxViewHidden
}
My viewDidLoad will also call this after setting up the uiviews in order to set up the collectionView:
private func configureCollectionView() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: UIHelper.createThreeColumnFlowLayout(in: view))
view.addSubview(collectionView)
collectionView.delegate = self
collectionView.backgroundColor = .systemBackground
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: CustomCell.resuseID)
collectionView.translatesAutoresizingMaskIntoConstraints = false
let bottomAnchor = isMaxViewHidden ? minView.bottomAnchor : maxView.bottomAnchor
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: bottomAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
Here is the function I call from each uiview click handler in an attempt to update the constraint:
private func resizeCollectionView() {
let bottomAnchor = isMaxViewHidden ? maxView.bottomAnchor: minView.bottomAnchor
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: bottomAnchor),
])
// This does move the collection view down, but seems hardcoded and bad
//collectionView.frame.origin.y = 650
}
Error:
"<NSLayoutConstraint:0x600000c4ed50 UIView:0x7ffd60c135e0.top == UILayoutGuide:0x6000016f89a0'UIViewSafeAreaLayoutGuide'.top (active)>",
"<NSLayoutConstraint:0x600000c4fa20 UIView:0x7ffd60c135e0.height == 250 (active)>",
"<NSLayoutConstraint:0x600000c4bed0 UIView:0x7ffd60c13750.top == UILayoutGuide:0x6000016f89a0'UIViewSafeAreaLayoutGuide'.top (active)>",
"<NSLayoutConstraint:0x600000c3c000 UIView:0x7ffd60c13750.height == 30 (active)>",
"<NSLayoutConstraint:0x600000c3dbd0 V:[UIView:0x7ffd60c135e0]-(0)-[UICollectionView:0x7ffd62819600] (active)>",
"<NSLayoutConstraint:0x600000c20cd0 V:[UIView:0x7ffd60c13750]-(0)-[UICollectionView:0x7ffd62819600] (active)>"
)
Will attempt to recover by breaking constraint

"<NSLayoutConstraint:0x600000c4ed50 UIView:0x7ffd60c135e0.top == UILayoutGuide:0x6000016f89a0'UIViewSafeAreaLayoutGuide'.top (active)>",
"<NSLayoutConstraint:0x600000c4fa20 UIView:0x7ffd60c135e0.height == 250 (active)>",
"<NSLayoutConstraint:0x600000c4bed0 UIView:0x7ffd60c13750.top == UILayoutGuide:0x6000016f89a0'UIViewSafeAreaLayoutGuide'.top (active)>",
"<NSLayoutConstraint:0x600000c3c000 UIView:0x7ffd60c13750.height == 30 (active)>",
What's happening in here is that, even though the view is being hidden, the constraints are still there, so assigning numerous top constraints on a single view will result to a conflict.
If you want to have a sort of dynamic constraint, just modify the constraint's constant or multiplier values. That way, you wont need to worry about constraint objects.
Wilson's solution is right. What I can just add from that is just have a single containerView containing your min and max views. and just toggle the visibility of your min and max view inside your containerView.
Here's my (simple) take on it:
everything is called in viewDidLoad() as you do, and
you also need to define a constraint object.
private var containerViewHeight: NSLayoutConstraint!
private func configureContainerView() {
containerView = UIView()
containerView.backgroundColor = .systemGreen
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(containerViewTapped)))
view.addSubview(containerView)
containerViewHeight = containerView.heightAnchor.constraint(equalToConstant: 250)
containerViewHeight.isActive = true
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
])
}
#objc private func containerViewTapped() {
isViewTapped.toggle()
containerViewHeight.constant = isViewTapped ? 30 : 250
innerMinView.isHidden = !isViewTapped
innerMaxView.isHidden = isViewTapped
}
private func configureCollectionView() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: UICollectionViewFlowLayout())
view.addSubview(collectionView)
collectionView.backgroundColor = .systemYellow
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: containerView.bottomAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
private func configureInnerMinView() {
innerMinView = UIView()
innerMinView.backgroundColor = .systemRed
innerMinView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(innerMinView)
NSLayoutConstraint.activate([
innerMinView.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.8),
innerMinView.heightAnchor.constraint(equalTo: containerView.heightAnchor, multiplier: 0.8),
innerMinView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
innerMinView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor)
])
}
private func configureInnerMaxView() {
innerMaxView = UIView()
innerMaxView.backgroundColor = .systemBlue
innerMaxView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(innerMaxView)
NSLayoutConstraint.activate([
innerMaxView.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.8),
innerMaxView.heightAnchor.constraint(equalTo: containerView.heightAnchor, multiplier: 0.8),
innerMaxView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
innerMaxView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor)
])
}
side note: btw. I think I know what project this is. :) SA-GH.F

You're activating conflicting constraints on top of each other. If you want to go between a minimized and maximized view, just change the constant value on the height constraint itself.
I whipped up a sample MyViewController in playgrounds with 2 views, the first of which's height and color changes when you tap it. myView is like your collapsable view, and myOtherView is like your collectionView.
Let me know if you have any questions!
class MyViewController : UIViewController {
private enum ViewState {
case min
case max
var height: CGFloat {
switch self {
case .min:
return 30
case .max:
return 250
}
}
var color: UIColor {
switch self {
case .min:
return .red
case .max:
return .green
}
}
}
private var myViewState: ViewState = .min {
didSet {
viewStateToggled()
}
}
private let myView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let myOtherView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .yellow
return view
}()
private lazy var myViewHeightConstraint = NSLayoutConstraint(
item: myView,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .notAnAttribute,
multiplier: 1,
constant: myViewState.height
)
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
configureMyViews()
}
private func configureMyViews() {
view.addSubview(myView)
myView.backgroundColor = myViewState.color
NSLayoutConstraint.activate([
myView.topAnchor.constraint(equalTo: view.topAnchor),
myView.leftAnchor.constraint(equalTo: view.leftAnchor),
myView.rightAnchor.constraint(equalTo: view.rightAnchor),
myViewHeightConstraint
])
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
myView.addGestureRecognizer(tap)
view.addSubview(myOtherView)
NSLayoutConstraint.activate([
myOtherView.topAnchor.constraint(equalTo: myView.bottomAnchor),
myOtherView.leftAnchor.constraint(equalTo: view.leftAnchor),
myOtherView.rightAnchor.constraint(equalTo: view.rightAnchor),
myOtherView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
private func viewStateToggled() {
myViewHeightConstraint.constant = myViewState.height
UIView.animate(withDuration: 0.4) {
self.myView.backgroundColor = self.myViewState.color
self.view.layoutSubviews()
}
}
#objc func handleTap(_ sender: UITapGestureRecognizer? = nil) {
myViewState = myViewState == .min ? .max : .min
}
}

I was wondering if you need a call to update your layout. Maybe called from the resizeCollectionView()?
let bottomAnchorMinContraint: NSConstraint = collectionView.topAnchor.constraint(equalTo: minView.bottomAnchor)
let bottomAnchorMaxContraint: NSConstraint = collectionView.topAnchor.constraint(equalTo: maxView.bottomAnchor)
if isMaxViewHidden {
bottomAnchorMinContraint.isActive = true
bottomAnchorMaxContraint.isActive = false
}
if !isMaxViewHidden {
bottomAnchorMinContraint.isActive = false
bottomAnchorMaxContraint.isActive = true
}
view.layoutIfNeeded()

Related

the view size gets smaller when the view is rotated

I have a view. when it's rotated, the view size gets smaller. would you please take a look at the pics that I've attached?
#IBAction func buttonForNewView(_ sender: Any) {
self.view.addSubview(customvView)
customvView.layer.cornerRadius = 15
customvView.backgroundColor = .gray
customvView.translatesAutoresizingMaskIntoConstraints = false
customvView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: view.frame.size.height*0.1).isActive = true
customvView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: view.frame.size.width*0.1).isActive = true
customvView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -view.frame.size.width*0.1).isActive = true
customvView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant:-view.frame.size.height*0.1).isActive = true
}
The constraints are not recalculated when the device is rotated.
This breaks out the constraint calculation into a separate function. Then it overrides traitCollectionDidChange(). When the trait collection changes, it removes the old constraints (to prevent auto layout from getting confused) and reapplies new constraints.
import UIKit
class ViewController: UIViewController {
var customvView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
customvView = UIView()
// Do any additional setup after loading the view.
}
func constrainCustomvView() {
customvView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: view.frame.size.height*0.1).isActive = true
customvView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: view.frame.size.width*0.1).isActive = true
customvView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -view.frame.size.width*0.1).isActive = true
customvView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant:-view.frame.size.height*0.1).isActive = true
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
if view.subviews.contains(customvView) { // for the case that the traits change before the view is created
// remove the existing constraits, or autolayout will have to choose between two and it will choose poorly
for constraint in view.constraints {
if let first = constraint.firstItem as? UIView, first == customvView {
view.removeConstraint(constraint)
}
if let second = constraint.secondItem as? UIView, second == customvView {
view.removeConstraint(constraint)
}
}
constrainCustomvView()
}
}
#IBAction func CustomButton(_ sender: Any) {
self.view.addSubview(customvView)
customvView.layer.cornerRadius = 15
customvView.backgroundColor = .gray
customvView.translatesAutoresizingMaskIntoConstraints = false
constrainCustomvView()
}
}
check this code.
let val = view.frame.size.height*0.1
self.view.addSubview(customvView)
customvView.layer.cornerRadius = 15
customvView.backgroundColor = .gray
customvView.translatesAutoresizingMaskIntoConstraints = false
customvView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: val).isActive = true
customvView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: val).isActive = true
customvView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -val).isActive = true
customvView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant:-val).isActive = true

How to set content hugging and compression programmatically

I want to animate hide show when one of my view is hidden. so I'm using content hugging priority to animate that, but it failed it has a gap between view. here I show you the ui and my code
This is 3 uiview code like the picture above
scrollView.addSubview(chooseScheduleDropDown)
chooseScheduleDropDown.translatesAutoresizingMaskIntoConstraints = false
chooseScheduleDropDown.setContentCompressionResistancePriority(.required, for: .vertical)
NSLayoutConstraint.activate([
chooseScheduleDropDown.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
chooseScheduleDropDown.topAnchor.constraint(equalTo: scrollView.topAnchor),
chooseScheduleDropDown.widthAnchor.constraint(equalToConstant: 285),
chooseScheduleDropDown.heightAnchor.constraint(equalToConstant: 60)
])
scrollView.addSubview(entryView)
entryView.isHidden = true
entryView.setContentHuggingPriority(.defaultLow, for: .vertical)
entryView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
entryView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
entryView.topAnchor.constraint(equalTo: chooseScheduleDropDown.bottomAnchor, constant: topPadding),
entryView.widthAnchor.constraint(equalToConstant: 285),
entryView.heightAnchor.constraint(equalToConstant: 60)
])
scrollView.addSubview(chooseDateView)
chooseDateView.setContentHuggingPriority(.defaultLow, for: .vertical)
chooseDateView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
chooseDateView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
chooseDateView.topAnchor.constraint(equalTo: entryView.bottomAnchor, constant: topPadding),
chooseDateView.widthAnchor.constraint(equalToConstant: 285),
chooseDateView.heightAnchor.constraint(equalToConstant: 60)
])
After exchanging comments, you have a number of different tasks to work on.
But, to give you an example of one approach to showing / hiding the "middle" view and having the bottom view move up / down, here is something to try. It will look like this:
Tapping the top (red) view will hide the middle (green) view and slide the bottom (blue) view up. Tapping the top (red) view again will slide the bottom (blue) view down and show the middle (green) view.
This is done by creating two top constraints for the Bottom view. One relative to the bottom of the Top view, and the other relative to the bottom of the Middle view, with different .priority values.
The example code is fairly straight-forward, and the comments should make things clear. All done via code - no #IBOutlet or #IBAction connections - so just create a new view controller and assign its custom class to AnimTestViewController:
class DropDownView: UIView {
}
class AnimTestViewController: UIViewController {
let scrollView: UIScrollView = {
let v = UIScrollView()
return v
}()
let chooseScheduleDropDown: DropDownView = {
let v = DropDownView()
return v
}()
let entryView: DropDownView = {
let v = DropDownView()
return v
}()
let chooseDateView: DropDownView = {
let v = DropDownView()
return v
}()
var visibleConstraint: NSLayoutConstraint = NSLayoutConstraint()
var hiddenConstraint: NSLayoutConstraint = NSLayoutConstraint()
override func viewDidLoad() {
super.viewDidLoad()
[chooseScheduleDropDown, entryView, chooseDateView].forEach {
v in
v.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(v)
}
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
let g = view.safeAreaLayoutGuide
let topPadding: CGFloat = 20.0
// chooseDateView top anchor when entryView is visible
visibleConstraint = chooseDateView.topAnchor.constraint(equalTo: entryView.bottomAnchor, constant: topPadding)
// chooseDateView top anchor when entryView is hidden
hiddenConstraint = chooseDateView.topAnchor.constraint(equalTo: chooseScheduleDropDown.bottomAnchor, constant: topPadding)
// we will start with entryView visible
visibleConstraint.priority = .defaultHigh
hiddenConstraint.priority = .defaultLow
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
chooseScheduleDropDown.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
chooseScheduleDropDown.topAnchor.constraint(equalTo: scrollView.topAnchor),
chooseScheduleDropDown.widthAnchor.constraint(equalToConstant: 285),
chooseScheduleDropDown.heightAnchor.constraint(equalToConstant: 60),
entryView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
entryView.topAnchor.constraint(equalTo: chooseScheduleDropDown.bottomAnchor, constant: topPadding),
entryView.widthAnchor.constraint(equalToConstant: 285),
entryView.heightAnchor.constraint(equalToConstant: 60),
chooseDateView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
//chooseDateView.topAnchor.constraint(equalTo: entryView.bottomAnchor, constant: topPadding),
visibleConstraint,
hiddenConstraint,
chooseDateView.widthAnchor.constraint(equalToConstant: 285),
chooseDateView.heightAnchor.constraint(equalToConstant: 60),
])
//entryView.isHidden = true
chooseScheduleDropDown.backgroundColor = .red
entryView.backgroundColor = .green
chooseDateView.backgroundColor = .blue
let tap = UITapGestureRecognizer(target: self, action: #selector(toggleEntryView(_:)))
chooseScheduleDropDown.addGestureRecognizer(tap)
}
#objc func toggleEntryView(_ sender: UITapGestureRecognizer) -> Void {
print("tapped")
// if entryView IS hidden we want to
// un-hide entryView
// animate alpha to 1.0
// animate chooseDateView down
// if entryView is NOT hidden we want to
// animate alpha to 0.0
// animate chooseDateView up
// hide entryView when animation is finished
let animSpeed = 0.5
if entryView.isHidden {
entryView.isHidden = false
hiddenConstraint.priority = .defaultLow
visibleConstraint.priority = .defaultHigh
UIView.animate(withDuration: animSpeed, animations: {
self.entryView.alpha = 1.0
self.view.layoutIfNeeded()
}, completion: { _ in
})
} else {
visibleConstraint.priority = .defaultLow
hiddenConstraint.priority = .defaultHigh
UIView.animate(withDuration: animSpeed, animations: {
self.entryView.alpha = 0.0
self.view.layoutIfNeeded()
}, completion: { _ in
self.entryView.isHidden = true
})
}
}
}
Do
// declare an instance property
var hCon:NSLayoutConstraint!
// get only the height constraint out of the activate block
hCon = entryView.heightAnchor.constraint(equalToConstant: 60)
hCon.isActive = true
and play with
hCon.constant = 300 / 0
view.layoutIfNeeded()

How to embed a UIViewController's view into another view without breaking responder chain

I'm trying to implement a base class for View Controllers that will add a top banner that is shown and hidden based on a specific status. Because my UIViewController extend this base class the view property is already loaded by the time I have to embed the original view property into a new view property that is created programmatically.
The embedding works fine (UI wise) the views adjust themselves appropriately however the issue that I'm seeing is that after embedding it the embedded views no longer respond to touch events, in the controller that I'm originally embedding I've got a table view with 16 rows and a button, before embedding it they both respond to tap and scroll events correctly.
I'm constrained to the fact that I cannot use a split view controller in IB to achieve the dual view split.
Here is my current implementation, can't seem to figure out what I'm missing to have event's propagate, I've tried using a custom view that overrides hitTest() to no avail for both newRootView and contentView variables.
Any help or insights are really appreciated, thanks!
class BaseViewController: UIViewController {
var isInOfflineMode: Bool {
didSet { if isInOfflineMode { embedControllerView() }}
}
var offlineBanner: UIView!
var contentView: UIView!
private func embedControllerView() {
guard let currentRoot = viewIfLoaded else { return }
currentRoot.translatesAutoresizingMaskIntoConstraints = false
let newRootView = UIView(frame: UIScreen.main.bounds)
newRootView.backgroundColor = .yellow // debugging
newRootView.isUserInteractionEnabled = true
newRootView.clipsToBounds = true
view = newRootView
offlineBanner = createOfflineBanner() // returns a button that I've verified to be tapable.
view.addSubview(offlineBanner)
contentView = UIView(frame: .zero)
contentView.backgroundColor = .cyan // debugging
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.isUserInteractionEnabled = true
view.addSubview(contentView)
contentView.addSubview(currentRoot)
let bannerHeight: CGFloat = 40.00
var topAnchor: NSLayoutYAxisAnchor
var bottomAnchor: NSLayoutYAxisAnchor
var trailingAnchor: NSLayoutXAxisAnchor
var leadingAnchor: NSLayoutXAxisAnchor
if #available(iOS 11.0, *) {
topAnchor = view.safeAreaLayoutGuide.topAnchor
bottomAnchor = view.safeAreaLayoutGuide.bottomAnchor
leadingAnchor = view.safeAreaLayoutGuide.leadingAnchor
trailingAnchor = view.safeAreaLayoutGuide.trailingAnchor
} else {
topAnchor = view.topAnchor
bottomAnchor = view.bottomAnchor
leadingAnchor = view.leadingAnchor
trailingAnchor = view.trailingAnchor
}
NSLayoutConstraint.activate([
offlineBanner.heightAnchor.constraint(equalToConstant: bannerHeight),
offlineBanner.topAnchor.constraint(equalTo: topAnchor),
offlineBanner.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0),
offlineBanner.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0),
])
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: topAnchor, constant: bannerHeight),
contentView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0),
contentView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0),
contentView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0),
])
OfflineViewController.migrateViewContraints(from: currentRoot, to: contentView)
view.setNeedsUpdateConstraints()
offlineBanner.setNeedsUpdateConstraints()
currentRoot.setNeedsUpdateConstraints()
}
private func unembedControllerView() {
let v = contentView.subviews[0]
v.removeFromSuperview()
view = v
OfflineViewController.migrateViewContraints(from: contentView, to: v)
}
/**
Replaces any constraints associated with the current root's safe area`UILayoutGuide` or with the actual
current root view.
*/
private static func migrateViewContraints(from currentRoot: UIView, to newRoot: UIView) {
for ct in currentRoot.constraints {
var firstItem: Any? = ct.firstItem
var secondItem: Any? = ct.secondItem
if #available(iOS 11.0, *) {
if firstItem as? UILayoutGuide == currentRoot.safeAreaLayoutGuide {
debugPrint("Migrating firstItem is currentLayoutGuide")
firstItem = newRoot.safeAreaLayoutGuide
}
if secondItem as? UILayoutGuide == currentRoot.safeAreaLayoutGuide {
debugPrint("Migrating secondItem is currentLayoutGuide")
secondItem = newRoot.safeAreaLayoutGuide
}
}
if firstItem as? UIView == currentRoot {
debugPrint("Migrating firstItem is currentRoot")
firstItem = newRoot
}
if secondItem as? UIView == currentRoot {
debugPrint("Migrating secondItem is currentRoot")
secondItem = newRoot
}
NSLayoutConstraint.deactivate([ct])
NSLayoutConstraint.activate([
NSLayoutConstraint(item: firstItem as Any,
attribute: ct.firstAttribute,
relatedBy: ct.relation,
toItem: secondItem,
attribute: ct.secondAttribute,
multiplier: ct.multiplier,
constant: ct.constant)
])
}
}
}
In this specific view the green buttons does get events it's a button that I create programmatically:
And here's the view that does not respond to events, a table view with a button:
I've figured what the issue was; I was missing constraints between contentView (the new root view) and the original self.view. The constraints essentially made the original currentRoot view take the full space of contentView:
NSLayoutConstraint.activate([
currentRoot.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0),
currentRoot.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0),
currentRoot.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0),
currentRoot.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0),
])

Swift textview growing direction

so i have a textview inside a uiview. And my question is how to make my textivew grow in up direction when textview goes to the next line as well as my uiview.
var textheightcontraint : NSLayoutConstraint!
var viewheightconstraint : NSLayoutConstraint!
func setup4(){
view.addSubview(colorview)
colorview.topAnchor.constraint(equalTo: customtableview.bottomAnchor).isActive = true
colorview.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
colorview.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
viewheightconstraint = colorview.heightAnchor.constraint(equalToConstant: 44)
viewheightconstraint.isActive = true
colorview.backgroundColor = UIColor.lightGray
colorview.addSubview(customtextview2)
customtextview2.backgroundColor = .white
customtextview2.leftAnchor.constraint(equalTo: colorview.leftAnchor).isActive = true
customtextview2.rightAnchor.constraint(equalTo: colorview.rightAnchor, constant: -20).isActive = true
customtextview2.bottomAnchor.constraint(equalTo: colorview.bottomAnchor).isActive = true
textheightcontraint = customtextview2.heightAnchor.constraint(equalToConstant: 39)
textheightcontraint.isActive = true
customtextview2.delegate = self
}
func setuptextview(){
let fixedWidth = customtextview2.frame.size.width
let newSize = customtextview2.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
self.textheightcontraint.constant = newSize.height
self.viewheightconstraint.constant = newSize.height
self.view.layoutIfNeeded()
}
func textViewDidChange(_ textView: UITextView) {
setuptextview()
}
If I understand your question, you want the text view (and the view it's contained in) to keep its bottom at the same position, and expand upward as you type?
To do that, disable scrolling in the text view, and set up your constraints so the bottom of the containing view is constrained to a y-position:
//
// ViewController.swift
//
// Created by Don Mag on 8/9/18.
//
import UIKit
class ViewController: UIViewController {
var theContainingView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
var theTextView: UITextView = {
let v = UITextView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .yellow
theContainingView.backgroundColor = .cyan
theContainingView.addSubview(theTextView)
view.addSubview(theContainingView)
NSLayoutConstraint.activate([
theTextView.topAnchor.constraint(equalTo: theContainingView.topAnchor, constant: 8.0),
theTextView.bottomAnchor.constraint(equalTo: theContainingView.bottomAnchor, constant: -8.0),
theTextView.leadingAnchor.constraint(equalTo: theContainingView.leadingAnchor, constant: 8.0),
theTextView.trailingAnchor.constraint(equalTo: theContainingView.trailingAnchor, constant: -8.0),
theContainingView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40.0),
theContainingView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40.0),
// Constrain the Bottom of the containing view. As the textView grows (or shrinks) with input,
// the TOP of the view will move up or down
theContainingView.bottomAnchor.constraint(equalTo: view.topAnchor, constant: 300.0),
])
theTextView.isScrollEnabled = false
theTextView.text = "This is the starting text."
}
}
Results:

Using ScrollView Programmatically in Swift 3

I have searched other questions and seem to still have some trouble creating my scrollView programmatically with autolayout in swift 3. I am able to get my scrollview to show up as shown in the picture below, but when I scroll to the bottom my other label does not show up and the 'scroll top' label does not disappear.
Hoping someone can help review my code below!
import UIKit
class ViewController: UIViewController {
let labelOne: UILabel = {
let label = UILabel()
label.text = "Scroll Top"
label.backgroundColor = .red
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let labelTwo: UILabel = {
let label = UILabel()
label.text = "Scroll Bottom"
label.backgroundColor = .green
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
let screensize: CGRect = UIScreen.main.bounds
let screenWidth = screensize.width
let screenHeight = screensize.height
var scrollView: UIScrollView!
scrollView = UIScrollView(frame: CGRect(x: 0, y: 120, width: screenWidth, height: screenHeight))
scrollView.contentSize = CGSize(width: screenWidth, height: 2000)
scrollView.addSubview(labelOne)
scrollView.addSubview(labelTwo)
view.addSubview(labelOne)
view.addSubview(labelTwo)
view.addSubview(scrollView)
// Visual Format Constraints
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": labelOne]))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-100-[v0]", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": labelOne]))
// Using iOS 9 Constraints in order to place the label past the iPhone 7 view
view.addConstraint(NSLayoutConstraint(item: labelTwo, attribute: .top, relatedBy: .equal, toItem: labelOne, attribute: .bottom, multiplier: 1, constant: screenHeight + 200))
view.addConstraint(NSLayoutConstraint(item: labelTwo, attribute: .right, relatedBy: .equal, toItem: labelOne, attribute: .right, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: labelTwo, attribute: .left, relatedBy: .equal, toItem: labelOne, attribute: .left, multiplier: 1, constant: 0)
}
}
It is easy to use constraints to define the scroll content size - so you don't have to do any manual calculations.
Just remember:
The content elements of your scroll view must have left / top / width / height values. In the case of objects such as labels, they have intrinsic sizes, so you only have to define the left & top.
The content elements of your scroll view also define the bounds of the scrollable area - the contentSize - but they do so with the bottom & right constraints.
Combining those two concepts, you see that you need a "continuous chain" with at least one element defining the top / left / bottom / right extents.
Here is a simple example, that will run directly in a Playground page:
import UIKit
import PlaygroundSupport
class TestViewController : UIViewController {
let labelOne: UILabel = {
let label = UILabel()
label.text = "Scroll Top"
label.backgroundColor = .red
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let labelTwo: UILabel = {
let label = UILabel()
label.text = "Scroll Bottom"
label.backgroundColor = .green
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let scrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
// add the scroll view to self.view
self.view.addSubview(scrollView)
// constrain the scroll view to 8-pts on each side
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 8.0).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8.0).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true
// add labelOne to the scroll view
scrollView.addSubview(labelOne)
// constrain labelOne to left & top with 16-pts padding
// this also defines the left & top of the scroll content
labelOne.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 16.0).isActive = true
labelOne.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 16.0).isActive = true
// add labelTwo to the scroll view
scrollView.addSubview(labelTwo)
// constrain labelTwo at 400-pts from the left
labelTwo.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 400.0).isActive = true
// constrain labelTwo at 1000-pts from the top
labelTwo.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 1000).isActive = true
// constrain labelTwo to right & bottom with 16-pts padding
labelTwo.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: -16.0).isActive = true
labelTwo.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -16.0).isActive = true
}
}
let vc = TestViewController()
vc.view.backgroundColor = .yellow
PlaygroundPage.current.liveView = vc
Edit - since this answer still gets occasional attention, I've updated the code to use more modern syntax, to respect the safe-area, and to use the scroll view's .contentLayoutGuide:
class TestViewController : UIViewController {
let labelOne: UILabel = {
let label = UILabel()
label.text = "Scroll Top"
label.backgroundColor = .yellow
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let labelTwo: UILabel = {
let label = UILabel()
label.text = "Scroll Bottom"
label.backgroundColor = .green
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let scrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
// add the scroll view to self.view
self.view.addSubview(scrollView)
// add labelOne to the scroll view
scrollView.addSubview(labelOne)
// add labelTwo to the scroll view
scrollView.addSubview(labelTwo)
// always a good idea to respect safe area
let safeG = view.safeAreaLayoutGuide
// we want to constrain subviews to the scroll view's Content Layout Guide
let contentG = scrollView.contentLayoutGuide
NSLayoutConstraint.activate([
// constrain the scroll view to safe area with 8-pts on each side
scrollView.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 8.0),
scrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 8.0),
scrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -8.0),
scrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: -8.0),
// constrain labelOne to leading & top of Content Layout Guide with 16-pts padding
// this also defines the left & top of the scroll content
labelOne.topAnchor.constraint(equalTo: contentG.topAnchor, constant: 16.0),
labelOne.leadingAnchor.constraint(equalTo: contentG.leadingAnchor, constant: 16.0),
// constrain labelTwo leading at 400-pts from labelOne trailing
labelTwo.leadingAnchor.constraint(equalTo: labelOne.trailingAnchor, constant: 400.0),
// constrain labelTwo top at 1000-pts from the labelOne bottom
labelTwo.topAnchor.constraint(equalTo: labelOne.bottomAnchor, constant: 1000),
// constrain labelTwo to trailing & bottom of Content Layout Guide with 16-pts padding
// this also defines the right & bottom of the scroll content
labelTwo.trailingAnchor.constraint(equalTo: contentG.trailingAnchor, constant: -16.0),
labelTwo.bottomAnchor.constraint(equalTo: contentG.bottomAnchor, constant: -16.0),
])
}
}
Two things.
1. Add the labels to scroll view, not your view
You want your label to scroll with scroll view, then you should not add it on your view. When running your code, you can scroll but the fixed label there is pinned to your view, not on your scroll view
2. Make sure you added your constraints correctly
Try it on your storyboard about what combination of constraint is enough for a view. At least 4 constraints are needed for a label.
Bottom line
Here is a modified version of your code. For constraint I added padding left, padding top, width and height and it works. My code is
let labelOne: UILabel = {
let label = UILabel()
label.text = "Scroll Top"
label.backgroundColor = .red
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let labelTwo: UILabel = {
let label = UILabel()
label.text = "Scroll Bottom"
label.backgroundColor = .green
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
let screensize: CGRect = UIScreen.main.bounds
let screenWidth = screensize.width
let screenHeight = screensize.height
var scrollView: UIScrollView!
scrollView = UIScrollView(frame: CGRect(x: 0, y: 120, width: screenWidth, height: screenHeight))
scrollView.addSubview(labelTwo)
NSLayoutConstraint(item: labelTwo, attribute: .leading, relatedBy: .equal, toItem: scrollView, attribute: .leadingMargin, multiplier: 1, constant: 10).isActive = true
NSLayoutConstraint(item: labelTwo, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 200).isActive = true
NSLayoutConstraint(item: labelTwo, attribute: .top, relatedBy: .equal, toItem: scrollView, attribute: .topMargin, multiplier: 1, constant: 10).isActive = true
NSLayoutConstraint(item: labelTwo, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 30).isActive = true
scrollView.contentSize = CGSize(width: screenWidth, height: 2000)
view.addSubview(scrollView)
}
And the scroll view looks like this
For me this work like charm
class WithDrawConfirmationViewController: UIViewController, WithDrawConfirmationViewProtocol {
var presenter: WithDrawConfirmationPresenterProtocol?
private let viewbackgroundColor = UIColor(hexString: "#F5F5F8")
// MARK:- View properties
lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
//view.backgroundColor = .red
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
lazy var contentView: UIView = {
let view = UIView()
//view.backgroundColor = .green
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var headerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private lazy var titleLabel:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor.themeBlack
label.text = "Withdraw Confirmation"//"Loading.."
label.textAlignment = .center
label.font = UIFont(name: "AvenirNext-DemiBold", size: 20)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
return label
}()
private lazy var descriptionLabel:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor.themeGray
label.text = "Please confirm your fixed deposit details before withdrawing your deposit"// "Loading.."
label.textAlignment = .center
label.font = UIFont(name: "AvenirNext-Medium", size: 14)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.setContentHuggingPriority(1000, for: .vertical)
return label
}()
private lazy var instructionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor.themeGray
label.text = "Complete fixed deposit will be withdrawn and the money will be debited to your account within 1-2 working days."//"Loading.."
label.textAlignment = .left
label.font = UIFont(name: "AvenirNext-Medium", size: 14)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
return label
}()
private lazy var warningLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor.themeBlue
label.text = "Axis bank will levy a penalty of 1% - 2% for premature withdrawal"//"Loading.."
label.textAlignment = .left
label.font = UIFont(name: "AvenirNext-DemiBold", size: 13)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
return label
}()
private lazy var warningView: UIView = {
let view = UIView()
view.cornerradius = 5
view.backgroundColor = UIColor(hexFromString: "#E8F4FD")
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private lazy var fdInformationDetailView: FDPurchaseDetailView = {
let view = FDPurchaseDetailView(isPoweredByViewVisible: false)
//view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private lazy var footerCTAView: FooterButtonViewView = {
let view = FooterButtonViewView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
// MARK:- Life cycle
override func viewDidLoad() {
super.viewDidLoad()
contentView.backgroundColor = viewbackgroundColor
scrollView.backgroundColor = viewbackgroundColor
setUpUI()
presenter?.viewDidLoadTriggered()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
private func setUpUI() {
view.addSubview(scrollView)
scrollView.addSubview(contentView)
view.backgroundColor = viewbackgroundColor
addFooter()
setUpHeaderView()
setUpFDDetailView()
setUpInformationAndWarningView()
//scrollView.backgroundColor = .lightGray
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: footerCTAView.topAnchor).isActive = true
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 100, right: 0)
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
contentView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
//contentView.bottomAnchor.constraint(equalTo: footerCTAView.topAnchor).isActive = true
}
private func setUpHeaderView() {
contentView.addSubview(headerView)
headerView.addSubviews([titleLabel, descriptionLabel])
headerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true
headerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
headerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4).isActive = true
titleLabel.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: 8).isActive = true
titleLabel.trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
titleLabel.topAnchor.constraint(equalTo: headerView.topAnchor, constant: 8).isActive = true
descriptionLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20).isActive = true
descriptionLabel.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: 8).isActive = true
descriptionLabel.trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
descriptionLabel.bottomAnchor.constraint(equalTo: headerView.bottomAnchor, constant: -20).isActive = true
}
private func setUpFDDetailView() {
contentView.addSubview(fdInformationDetailView)
fdInformationDetailView.topAnchor.constraint(equalTo: headerView.bottomAnchor).isActive = true
fdInformationDetailView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true
fdInformationDetailView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
}
private func setUpInformationAndWarningView() {
contentView.addSubview(instructionLabel)
instructionLabel.topAnchor.constraint(equalTo: fdInformationDetailView.bottomAnchor, constant: 20).isActive = true
instructionLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true
instructionLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
warningView.addSubview(warningLabel)
warningLabel.topAnchor.constraint(equalTo: warningView.topAnchor, constant: 8).isActive = true
warningLabel.leadingAnchor.constraint(equalTo: warningView.leadingAnchor, constant: 20).isActive = true
warningLabel.trailingAnchor.constraint(equalTo: warningView.trailingAnchor, constant: -20).isActive = true
warningLabel.bottomAnchor.constraint(equalTo: warningView.bottomAnchor, constant: -8).isActive = true
contentView.addSubview(warningView)
warningView.topAnchor.constraint(equalTo: instructionLabel.bottomAnchor, constant: 8).isActive = true
warningView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true
warningView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
//warningView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
warningView.bottomAnchor.constraint(greaterThanOrEqualTo: contentView.bottomAnchor, constant: -20).isActive = true
}
private func addFooter() {
view.addSubview(footerCTAView)
footerCTAView.delegate = self
footerCTAView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
footerCTAView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
footerCTAView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
}
}
Set scrollview images to the wallpaper:
#IBOutlet var scroll_view_img: UIScrollView!
var itemPhotoList = NSMutableArray()
var button = NSMutableArray()
#IBOutlet var imageview_big: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
itemPhotoList = ["grief-and-loss copy.jpg","aaa.jpg","image_4.jpeg"]
// button = ["btn1","btn2"]
let width:CGFloat = 100
let height:CGFloat = 100
var xposition:CGFloat = 10
var scroll_contont:CGFloat = 0
for i in 0 ..< itemPhotoList.count
{
var button_img = UIButton()
button_img = UIButton(frame: CGRect(x: xposition, y: 50, width: width, height: height))
let img = UIImage(named:itemPhotoList[i] as! String)
button_img.setImage(img, for: .normal)
scroll_view_img.addSubview(button_img)
button_img.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
button_img.tag = i
view.addSubview(scroll_view_img)
xposition += width+10
scroll_contont += width
scroll_view_img.contentSize = CGSize(width: scroll_contont, height: height)
}
}
func buttonAction(sender: UIButton!)
{
switch sender.tag {
case 0:
imageview_big.image = UIImage(named: "grief-and-loss copy.jpg")
case 1:
imageview_big.image = UIImage(named: "aaa.jpg")
case 2:
imageview_big.image = UIImage(named: "image_4.jpeg")
default:
break
}
}
Copy and paste this controller in your project
class BaseScrollViewController: UIViewController {
lazy var contentViewSize = CGSize(width: self.view.frame.width, height: self.view.frame.height + 100)
lazy var scrollView: UIScrollView = {
let view = UIScrollView(frame: .zero)
view.backgroundColor = .white
view.frame = self.view.bounds
view.contentSize = contentViewSize
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var containerView: UIView = {
let v = UIView()
v.backgroundColor = .white
v.frame.size = contentViewSize
return v
}()
override func viewDidLoad() {
view.backgroundColor = .white
view.addSubview(scrollView)
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollView.addSubview(containerView)
setupContainer(containerView)
super.viewDidLoad()
}
public func setupContainer(_ container: UIView) {
}
}
Usage for above code:
class ClientViewController: BaseScrollViewController {
override func viewDidLoad() {
super.viewDidLoad()
// do your stuff here
}
override func setupContainer(_ container: UIView) {
// add views here
}
}
These answers do not work with large titles in the navigation bar. Make sure you have the code below in your viewDidLoad() method of your view controller:
self.navigationController?.navigationBar.prefersLargeTitles = false

Resources