ScrollView not scrolling when label text is big string - ios

I'm trying to create a scroll view programatically, but it is not working as expected. Here is my code.
let scrollView = UIScrollView()
view.addSubview(scrollView)
scrollView.leadingAnchor(equalTo: view.leadingAnchor)
scrollView.trailingAnchor(equalTo: view.trailingAnchor)
scrollView.topAnchor(equalTo: view.topAnchor)
scrollView.bottomAnchor(equalTo: view.bottomAnchor)
let imageview = UIImageView()
scrollView.addSubview(imageview)
let contentLabel = UILabel()
scrollView.addSubview(contentLabel)
imageview.leadingAnchor(equalTo: view.leadingAnchor)
imageview.trailingAnchor(equalTo: view.trailingAnchor)
imageview.topAnchor(equalTo: view.topAnchor)
imageview.heightAnchor(equalTo: 300)
imageview.backgroundColor = .cyan
contentLabel.numberOfLines = 0
contentLabel.leadingAnchor(equalTo: view.leadingAnchor)
contentLabel.trailingAnchor(equalTo: view.trailingAnchor)
contentLabel.topAnchor(equalTo: imageview.bottomAnchor, constant: 16)
contentLabel.bottomAnchor(equalTo: scrollView.bottomAnchor, constant: -16)
contentLabel.text = BIG_TEXT
I'm trying to add an image view and text below that. When the text content is more I should make it scrollable. Thanks in advance.!!

Try to add a UIStackView as the first child of your UIScrollView and add your subviews to it with the addArrangedSubview method:
let scrollView = UIScrollView()
view.addSubview(scrollView)
scrollView.leadingAnchor(equalTo: view.leadingAnchor)
scrollView.trailingAnchor(equalTo: view.trailingAnchor)
scrollView.topAnchor(equalTo: view.topAnchor)
scrollView.bottomAnchor(equalTo: view.bottomAnchor)
let stackView = UIStackView()
stackView.axis = .vertical
stackView.leadingAnchor(equalTo: scrollView.leadingAnchor)
stackView.trailingAnchor(equalTo: scrollView.trailingAnchor)
stackView.topAnchor(equalTo: scrollView.topAnchor)
stackView.bottomAnchor(equalTo: scrollView.bottomAnchor)
stackView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor)
let imageview = UIImageView()
imageview.heightAnchor(equalTo: 300)
imageview.backgroundColor = .cyan
stackView.addArrangedSubview(imageview)
let contentLabel = UILabel()
contentLabel.text = BIG_TEXT
contentLabel.numberOfLines = 0
stackView.addArrangedSubview(contentLabel)
stackView.layoutIfNeeded()

Related

I want to set label & Imageview in centre of myView but App crashing after adding some constraints

Error:-
Thread 1: "Unable to activate constraint with anchors <NSLayoutXAxisAnchor:0x280af8500 \"UILabel:0x103dc4fa0.centerX\"> and <NSLayoutXAxisAnchor:0x280af89c0 \"UIView:0x103dc49d0.centerX\"> because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal."
I've created a function programmatical in Base view controller which returns a view & I've added some constraints to its
Function:-
func getMarker (lbl:String, img:UIImage) -> UIView {
let myView = UIView(frame: CGRect.zero)
myView.center = CGPoint(x: 50, y: 160)
myView.backgroundColor = UIColor.clear
let imageView = UIImageView(image: img)
imageView.frame = CGRect(x: 0, y: 0, width: 20, height: 40)
myView.addSubview(imageView)
let label = UILabel(frame: CGRect(x: 0, y: 45, width: 120, height: 30))
label.text = lbl
label.textAlignment = .center
label.adjustsFontSizeToFitWidth = true
label.textColor = UIColor.black
label.backgroundColor = UIColor.white
label.layer.borderColor = UIColor.lightGray.cgColor
label.layer.borderWidth = 0.5
label.layer.cornerRadius = 5
label.layer.masksToBounds = true
label.sizeToFit()
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: myView.centerXAnchor),
imageView.centerXAnchor.constraint(equalTo: myView.centerXAnchor)
])
myView.addSubview(label)
return myView
}
calling function in another controller but it crashing and showing me the error which I mentioned above
Calling function:-
getMarker(lbl: device.name ?? "", img: (UIImage(named: icfile) ?? UIImage(named: "truck_1_orange")!))
U need to add subview first, then activate layout. Label is not in myView subviews in your code. It is not in any hierarchy at the moment of layout constraint activation.
myView.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: myView.centerXAnchor),
imageView.centerXAnchor.constraint(equalTo: myView.centerXAnchor)
])
You need to configure the layout of your subviews (imageView and label) in order to have them sized and positioned where you want them.
Take a look at this example code:
class MarkerVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
guard let img = UIImage(named: "img80x80")
else {
fatalError("Could not load sample image!!!")
}
let markerView1 = getMarker(lbl: "Testing", img: img)
let markerView2 = getMarkerAutoSized(lbl: "Testing", img: img)
markerView1.translatesAutoresizingMaskIntoConstraints = false
markerView2.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(markerView1)
view.addSubview(markerView2)
// respect safe area
let safeG = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// we have to set both Position and Size constraints
// for markerView1
markerView1.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 20.0),
markerView1.widthAnchor.constraint(equalToConstant: 240.0),
markerView1.heightAnchor.constraint(equalToConstant: 100.0),
markerView1.centerXAnchor.constraint(equalTo: view.centerXAnchor),
// markerView2 uses its subviews to set its own size
// so we only need position constraints
markerView2.topAnchor.constraint(equalTo: markerView1.bottomAnchor, constant: 20.0),
markerView2.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
// so we can see the view frames
markerView1.backgroundColor = .systemRed
markerView2.backgroundColor = .systemRed
}
func getMarker (lbl:String, img:UIImage) -> UIView {
let myView = UIView()
myView.backgroundColor = UIColor.clear
let imageView = UIImageView(image: img)
let label = UILabel()
label.text = lbl
label.textAlignment = .center
label.adjustsFontSizeToFitWidth = true
label.textColor = UIColor.black
label.backgroundColor = UIColor.white
label.layer.borderColor = UIColor.lightGray.cgColor
label.layer.borderWidth = 0.5
label.layer.cornerRadius = 5
label.layer.masksToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
myView.addSubview(imageView)
myView.addSubview(label)
NSLayoutConstraint.activate([
// image view Width and Height
imageView.widthAnchor.constraint(equalToConstant: 20.0),
imageView.heightAnchor.constraint(equalToConstant: 40.0),
// labe Width and Height
label.widthAnchor.constraint(equalToConstant: 120.0),
label.heightAnchor.constraint(equalToConstant: 30.0),
// image view aligned to Top
imageView.topAnchor.constraint(equalTo: myView.topAnchor),
// centered Horizontally
imageView.centerXAnchor.constraint(equalTo: myView.centerXAnchor),
// label 5-pts below image view
label.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 5.0),
// centered Horizontally
label.centerXAnchor.constraint(equalTo: myView.centerXAnchor),
])
return myView
}
func getMarkerAutoSized (lbl:String, img:UIImage) -> UIView {
let myView = UIView()
myView.backgroundColor = UIColor.clear
let imageView = UIImageView(image: img)
let label = UILabel()
label.text = lbl
label.textAlignment = .center
label.adjustsFontSizeToFitWidth = true
label.textColor = UIColor.black
label.backgroundColor = UIColor.white
label.layer.borderColor = UIColor.lightGray.cgColor
label.layer.borderWidth = 0.5
label.layer.cornerRadius = 5
label.layer.masksToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
myView.addSubview(imageView)
myView.addSubview(label)
NSLayoutConstraint.activate([
// image view Width and Height
imageView.widthAnchor.constraint(equalToConstant: 20.0),
imageView.heightAnchor.constraint(equalToConstant: 40.0),
// labe Width and Height
label.widthAnchor.constraint(equalToConstant: 120.0),
label.heightAnchor.constraint(equalToConstant: 30.0),
// image view aligned to Top
imageView.topAnchor.constraint(equalTo: myView.topAnchor),
// centered Horizontally
imageView.centerXAnchor.constraint(equalTo: myView.centerXAnchor),
// label 5-pts below image view
label.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 5.0),
// centered Horizontally
label.centerXAnchor.constraint(equalTo: myView.centerXAnchor),
// to auto-size myView
label.leadingAnchor.constraint(equalTo: myView.leadingAnchor),
label.trailingAnchor.constraint(equalTo: myView.trailingAnchor),
label.bottomAnchor.constraint(equalTo: myView.bottomAnchor),
])
return myView
}
}
I modified your getMarker(...) func to size and position the subviews.
I also added a very similar getMarkerAutoSized(...) func that uses a couple additional constraints on the subviews.
For the first version, we must set both the Position and Size of the generated view.
For the second version, we only need to set the generated view's Position because it sizes itself to fit its subviews.
Here's how they look:

(Swift 5) UIScrollView scrolls but none of the content scrolls (video included)

I'm trying to learn to build views without storyboard. I tried to build a scrollview. On that scrollview is a UISearchBar, a UIImageView with an image and a UILabel. It works but none of the content moves. The content is all just frozen in place like no matter how far I scroll the search bar will always be on top of the page. and the image on the bottom. I've attached a video to show what I mean. There's also a problem because none of the content is where I want it to be but that's another problem. I realize this is probably because I don't know enough about constraints and autolayout and building views without storyboards.
Here's the video
class HomePageViewController: UIViewController {
var searchedText: String = ""
let label = UILabel()
let searchBar: UISearchBar = {
let searchBar = UISearchBar()
searchBar.placeholder = "Where are you going?"
searchBar.translatesAutoresizingMaskIntoConstraints = false
searchBar.barTintColor = .systemCyan
searchBar.searchTextField.backgroundColor = .white
searchBar.layer.cornerRadius = 5
return searchBar
}()
let homeImage: UIImageView = {
let homeImage = UIImageView()
homeImage.translatesAutoresizingMaskIntoConstraints = false
homeImage.clipsToBounds = true
return homeImage
}()
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.backgroundColor = .systemMint
scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * 30)
return scrollView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemPink
// setupLayout()
// tried this here doesn't do anything for me
}
func setupLayout() {
view.addSubview(scrollView)
self.scrollView.addSubview(searchBar)
homeImage.image = UIImage(named: "Treehouse")
self.scrollView.addSubview(homeImage)
label.text = "Inspiration for your next trip..."
self.scrollView.addSubview(label)
// not sure where this label is being added I want it to be underneath the image but it isn't t
let safeG = view.safeAreaLayoutGuide
let viewFrame = view.bounds
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: -10),
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor),
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
searchBar.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 50.0),
searchBar.widthAnchor.constraint(equalTo: safeG.widthAnchor, multiplier: 0.9),
searchBar.centerXAnchor.constraint(equalTo: safeG.centerXAnchor),
homeImage.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 150),
homeImage.widthAnchor.constraint(equalTo: safeG.widthAnchor, multiplier: 1.1),
homeImage.centerXAnchor.constraint(equalTo: safeG.centerXAnchor),
homeImage.heightAnchor.constraint(equalToConstant: viewFrame.height/2),
label.topAnchor.constraint(equalTo: homeImage.bottomAnchor, constant: 100)
])
// was doing all this in viewDidLayoutSubviews but not sure if this is better place for it
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setupLayout()
// tried this in viewDidLoad() and it didn't solve it.
}
}
any help would be appreciated
First, when constraining subviews in a UIScrollView, you should constrain them to the scroll view's Content Layout Guide. You're constraining them to the view's safe area layout guide, so they're never going to go anywhere.
Second, it's difficult to center subviews in a scroll view, because the scroll view can scroll both horizontally and vertically. So it doesn't really have a "center."
You can either put subviews in a stack view, or, quite common, use a UIView as a "content" view to hold the subviews. If you constrain that content view's Width to the scroll view's Frame Layout Guide width, you can then horizontally center the subviews.
Third, it can be very helpful to comment your constraints, so you know exactly what you expect them to do.
Here's a modified version of your posted code:
class HomePageViewController: UIViewController {
var searchedText: String = ""
let label: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let searchBar: UISearchBar = {
let searchBar = UISearchBar()
searchBar.placeholder = "Where are you going?"
searchBar.translatesAutoresizingMaskIntoConstraints = false
searchBar.barTintColor = .systemCyan
searchBar.searchTextField.backgroundColor = .white
searchBar.layer.cornerRadius = 5
return searchBar
}()
let homeImage: UIImageView = {
let homeImage = UIImageView()
homeImage.translatesAutoresizingMaskIntoConstraints = false
homeImage.clipsToBounds = true
return homeImage
}()
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.backgroundColor = .systemMint
// don't do this
//scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * 30)
return scrollView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemPink
setupLayout()
}
func setupLayout() {
view.addSubview(scrollView)
//homeImage.image = UIImage(named: "Treehouse")
homeImage.image = UIImage(named: "natureBKG")
label.text = "Inspiration for your next trip..."
// let's use a UIView to hold the "scroll content"
let contentView = UIView()
contentView.translatesAutoresizingMaskIntoConstraints = false
// give it a green background so we can see it
contentView.backgroundColor = .green
contentView.addSubview(searchBar)
contentView.addSubview(homeImage)
contentView.addSubview(label)
scrollView.addSubview(contentView)
let safeG = view.safeAreaLayoutGuide
let svContentG = scrollView.contentLayoutGuide
let svFrameG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// constrain scrollView to all 4 sides of view
// (generally, constrain to safe-area, but this is what you had)
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor),
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
// constrain contentView to all 4 sides of scroll view's Content Layout Guide
contentView.topAnchor.constraint(equalTo: svContentG.topAnchor, constant: 0.0),
contentView.leadingAnchor.constraint(equalTo: svContentG.leadingAnchor, constant: 0.0),
contentView.trailingAnchor.constraint(equalTo: svContentG.trailingAnchor, constant: 0.0),
contentView.bottomAnchor.constraint(equalTo: svContentG.bottomAnchor, constant: 0.0),
// constrain contentView Width equal to scroll view's Frame Layout Guide Width
contentView.widthAnchor.constraint(equalTo: svFrameG.widthAnchor),
// constrain searchBar Top to contentView Top + 50
searchBar.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 50.0),
// constrain searchBar Width to 90% of contentView Width
searchBar.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.9),
// constrain searchBar centerX to contentView centerX
searchBar.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// constrain homeImage Top to searchBar Bottom + 40
homeImage.topAnchor.constraint(equalTo: searchBar.bottomAnchor, constant: 40.0),
// constrain homeImage Width equal to contentView Width
homeImage.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 1.0),
// constrain homeImage centerX to contentView centerX
homeImage.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// constrain homeImage Height to 1/2 of scroll view frame Height
homeImage.heightAnchor.constraint(equalTo: svFrameG.heightAnchor, multiplier: 0.5),
// you probably won't get vertical scrolling yet, so increase the vertical space
// between the homeImage and the label by changing the constant
// from 100 to maybe 400
// constrain label Top to homeImage Bottom + 100
label.topAnchor.constraint(equalTo: homeImage.bottomAnchor, constant: 100.0),
// constrain label centerX to contentView centerX
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// constrain label Bottom to contentView Bottom - 20
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20.0),
])
}
}

Two dynamic Stackviews in Scrollview

Here is my view's hierarchy
ScrollView
-> View
-> Stackview1
-> view1
-> view2
.
.
.
-> Stackview1
-> view1
-> view2
.
.
.
So here my stackview's height is generated dynamically as per the views added on .swift file
If I give bottom constraint to the super view it is working only with single stackview but If I use both stackviews it is not considering to the second stackview.
It's a bit hard to tell what the problem is, because you have not posted your constraints, but the solution is pretty straight forward:
You need the following constraints:
ScrollView:
topAnchor to view.topAnchor
leadingAnchor to view.leadingAnchor
trailingAnchor to view.trailingAnchor
bottomAnchor to view.bottomAnchor
UIView that wraps the 2 UIStackViews:
topAnchor to scrollView.topAnchor
leadingAnchor to scrollView.leadingAnchor
trailingAnchor to scrollView.trailingAnchor
bottomAnchor to scrollView.bottomAnchor
Upper StackView:
topAnchor to wrapperView.topAnchor
leadingAnchor to wrapperView.leadingAnchor
trailingAnchor to wrapperView.trailingAnchor
widthAnchor to scrollView.widthAnchor (to define the width of the ScrollView's contentSize)
Lower StackView:
topAnchor to upperStackView.bottomAnchor
leadingAnchor to wrapperView.leadingAnchor
trailingAnchor to wrapperView.trailingAnchor
bottomAnchor to wrapperView.bottomAnchor
Now when you add a UIView to one of the UIStackViews the UIScrollView gets updated automatically.
Here is a working example: (I added two UIButtons to add more UIViews to both UIStackViews)
class ViewController: UIViewController {
let upperStackView = UIStackView()
let lowerStackView = UIStackView()
override func viewDidLoad() {
super.viewDidLoad()
let scrollView = UIScrollView()
view.addSubview(scrollView)
scrollView.backgroundColor = .white
scrollView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
let wrapperView = UIView()
scrollView.addSubview(wrapperView)
wrapperView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
wrapperView.topAnchor.constraint(equalTo: scrollView.topAnchor),
wrapperView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
wrapperView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
wrapperView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
])
upperStackView.axis = .vertical
upperStackView.distribution = .equalSpacing
upperStackView.alignment = .fill
wrapperView.addSubview(upperStackView)
upperStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
upperStackView.topAnchor.constraint(equalTo: wrapperView.topAnchor),
upperStackView.leadingAnchor.constraint(equalTo: wrapperView.leadingAnchor),
upperStackView.trailingAnchor.constraint(equalTo: wrapperView.trailingAnchor),
upperStackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
])
lowerStackView.axis = .vertical
lowerStackView.distribution = .equalSpacing
lowerStackView.alignment = .fill
wrapperView.addSubview(lowerStackView)
lowerStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
lowerStackView.topAnchor.constraint(equalTo: upperStackView.bottomAnchor),
lowerStackView.leadingAnchor.constraint(equalTo: wrapperView.leadingAnchor),
lowerStackView.trailingAnchor.constraint(equalTo: wrapperView.trailingAnchor),
lowerStackView.bottomAnchor.constraint(equalTo: wrapperView.bottomAnchor)
])
for _ in 0...3 {
addView(to: upperStackView)
addView(to: lowerStackView)
}
let lowerButton = UIButton(type: .system)
lowerButton.setTitle("Add to lower StackView", for: .normal)
lowerButton.backgroundColor = .white
lowerButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(lowerButton)
NSLayoutConstraint.activate([
lowerButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -60),
lowerButton.widthAnchor.constraint(equalToConstant: 240),
lowerButton.heightAnchor.constraint(equalToConstant: 44),
lowerButton.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
lowerButton.addTarget(self, action: #selector(addViewToLowerStackView), for: .touchUpInside)
let upperButton = UIButton(type: .system)
upperButton.setTitle("Add to upper StackView", for: .normal)
upperButton.backgroundColor = .white
upperButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(upperButton)
NSLayoutConstraint.activate([
upperButton.bottomAnchor.constraint(equalTo: lowerButton.topAnchor, constant: -20),
upperButton.widthAnchor.constraint(equalToConstant: 240),
upperButton.heightAnchor.constraint(equalToConstant: 44),
upperButton.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
upperButton.addTarget(self, action: #selector(addViewToUpperStackView), for: .touchUpInside)
}
func addView(to stackView: UIStackView) {
let view = UIView()
view.backgroundColor = stackView == upperStackView ? .blue : .green
view.alpha = CGFloat(stackView.arrangedSubviews.count + 1) * 0.1
view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([view.heightAnchor.constraint(equalToConstant: 120)])
stackView.addArrangedSubview(view)
}
#objc func addViewToUpperStackView() {
addView(to: upperStackView)
}
#objc func addViewToLowerStackView() {
addView(to: lowerStackView)
}
}
UPDATE:
You could also make this work using only a Storyboard:

How to add extra padding to UIScrollView on iPad programmatically?

Some of the applications add extra padding on left and right to the UIScrollView instances when the device width is too large.
What's important, content of the UIScrollView is not restricted to fixed width, because user can scroll outside of the content. It looks like it would have contentInset set, which dynamically changes based on superview width. How can I achieve it?
Note
Screen from above is not implemented as a scrolling view, I just wanted to visualize the concept.
If it is an option at all, you can adjust left and right (trailing, leading) autolayout constraints.
The following code creates horizontally scrollable scrollView with two containers in it. The first container contains a subview (red rectangle) with some margins.
Scroll View with container.
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
private let scrollView = UIScrollView()
private let contentView = UIView()
override func loadView() {
let view = UIView()
self.view = view
self.view.backgroundColor = UIColor.white
scrollView.isPagingEnabled = true
scrollView.isScrollEnabled = true
scrollView.isUserInteractionEnabled = true
scrollView.bounces = true
self.view.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: self.view.topAnchor),
scrollView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
scrollView.rightAnchor.constraint(equalTo: self.view.rightAnchor)
])
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.backgroundColor = UIColor(white: 0.9, alpha: 0.9)
scrollView.addSubview(contentView)
let widthAnchor = contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
let heightAnchor = contentView.heightAnchor.constraint(equalTo: scrollView.heightAnchor)
widthAnchor.priority = .defaultLow
heightAnchor.priority = .defaultLow
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentView.leftAnchor.constraint(equalTo: scrollView.leftAnchor),
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
contentView.rightAnchor.constraint(equalTo: scrollView.rightAnchor),
widthAnchor,
heightAnchor
])
contentView.layer.borderWidth = 1
contentView.layer.borderColor = UIColor.red.cgColor
let containerView1 = UIView()
containerView1.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(containerView1)
containerView1.backgroundColor = UIColor(white: 0.4, alpha: 1)
NSLayoutConstraint.activate([
containerView1.topAnchor.constraint(equalTo: contentView.topAnchor),
containerView1.leftAnchor.constraint(equalTo: contentView.leftAnchor),
containerView1.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
containerView1.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
])
let container1Content = container1ContentView()
containerView1.addSubview(container1Content)
NSLayoutConstraint.activate([
container1Content.topAnchor.constraint(equalTo: containerView1.topAnchor, constant: 44),
container1Content.leftAnchor.constraint(equalTo: containerView1.leftAnchor, constant: 44),
container1Content.rightAnchor.constraint(equalTo: containerView1.rightAnchor, constant: -44),
container1Content.bottomAnchor.constraint(equalTo: containerView1.bottomAnchor, constant: -44)
])
let containerView2 = UIView()
containerView2.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(containerView2)
containerView2.backgroundColor = UIColor(white: 0.9, alpha: 1)
NSLayoutConstraint.activate([
containerView2.topAnchor.constraint(equalTo: contentView.topAnchor),
containerView2.leftAnchor.constraint(equalTo: containerView1.rightAnchor),
containerView2.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
containerView2.rightAnchor.constraint(equalTo: contentView.rightAnchor),
containerView2.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
])
}
private func container1ContentView() -> UIView {
let content = UIView()
content.translatesAutoresizingMaskIntoConstraints = false
content.backgroundColor = UIColor.red
return content
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

How to position rotated UILabels inside a UIStackView with no gap?

I'm trying to create a UIStackView containing UILabels that have been rotated. The UIStackView is positioning the UILabels based on their non-rotated widths, such that there is a large gap when I would expect the UILabels to be directly adjacent. How can I correctly reposition these elements?
Code:
let stackView = UIStackView(frame: canvas.bounds)
stackView.translatesAutoresizingMaskIntoConstraints = false
canvas.addSubview(stackView)
stackView.axis = .horizontal
stackView.topAnchor.constraint(equalTo: canvas.topAnchor, constant: 0).isActive = true
stackView.bottomAnchor.constraint(equalTo: canvas.bottomAnchor, constant: 0).isActive = true
stackView.leftAnchor.constraint(equalTo: canvas.leftAnchor, constant: 0).isActive = true
let label1 = UILabel()
label1.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
label1.text = "THREEEEEEEE"
stackView.addArrangedSubview(label1)
let label2 = UILabel()
label2.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
label2.text = "FOUR"
stackView.addArrangedSubview(label2)
What I'm Seeing:
UI Inspector View:
What I Expect:
Why don't you try to add the views to the stack view first, and then rotate the stack view?
let label = UILabel()
label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.text = "Hello World!"
label.textColor = .black
view.addSubview(label)
self.view = view
let label1 = UILabel()
label1.text = "THREEEEEEEE"
let label2 = UILabel()
label2.text = "FOUR"
let stackView = UIStackView(arrangedSubviews: [label1, label2])
stackView.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
stackView.axis = .vertical
stackView.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 100).isActive = true
I used the view instead of the canvas.

Resources