Vertically rotated UIPageControl taking too much space - ios

I used CGAffineTransform to rotate a horizontal UIPageControl vertical. But when I added it besides my collection view it's taking too much width. And when I add a width anchor on it, the UIPageControl disappears.
noticesPagingIndicator = UIPageControl()
let angle = CGFloat.pi/2
noticesPagingIndicator.transform = CGAffineTransform(rotationAngle: angle)
NSLayoutConstraint.activate([
// noticesPagingIndicator.widthAnchor.constraint(equalToConstant: 30),
noticesPagingIndicator.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
noticesPagingIndicator.centerYAnchor.constraint(equalTo: noticesCollectionView.centerYAnchor),
noticesCollectionView.leadingAnchor.constraint(equalTo: noticesPagingIndicator.trailingAnchor),
noticesCollectionView.topAnchor.constraint(equalTo: noticeStackView.bottomAnchor, constant: 8),
noticesCollectionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
noticesCollectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
])
When I look at the UIView hierarchy, I see a lot of padding along the UIPageControl
With the width anchor enabled:

Get to know the Debug View Hierarchy tool. It can help you figure out most layout issues.
When you transform a view, that doesn't change its bounds and thus doesn't change its constraint relationships to other UI elements.
With this code (8 pages, so 8 dots):
class ViewController: UIViewController {
let pgc = UIPageControl()
let greenLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
pgc.translatesAutoresizingMaskIntoConstraints = false
greenLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(pgc)
view.addSubview(greenLabel)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// page control Leading to safe area Leading + 20, centerY
pgc.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
pgc.centerYAnchor.constraint(equalTo: g.centerYAnchor, constant: 0.0),
// constrain greenLabel Leading to page control trailing + 8 and centerY, safe area trailing -8
greenLabel.leadingAnchor.constraint(equalTo: pgc.trailingAnchor, constant: 8.0),
greenLabel.centerYAnchor.constraint(equalTo: pgc.centerYAnchor),
greenLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
])
// rotate the page control
let angle = CGFloat.pi/2
pgc.transform = CGAffineTransform(rotationAngle: angle)
pgc.backgroundColor = .systemBlue
greenLabel.backgroundColor = .green
pgc.numberOfPages = 8
greenLabel.numberOfLines = 0
greenLabel.text = "UIPageControl indicates the number of open pages in an application by displaying a dot for each open page. The dot that corresponds to the currently viewed page is highlighted. UIPageControl supports navigation by sending the delegate an event when a user taps to the right or to the left of the currently highlighted dot."
}
}
You get this output:
As you've seen, the Green Label Leading constraint to the page control Trailing Anchor shows the page control width matches what it would be without the rotation.
If you inspect the views with Debug View Hierarchy, you'll see the page control looks like this:
The frame is w: 27.5 h: 217 but the bounds is w: 217 h: 27.5.
To fix this, you need to embed the page control in a "holder" view, constrain the holder view's Height to the page control's Width and Width to Height. Then constrain your other elements to that "holder" view:
class ViewController: UIViewController {
let pgcHolder = UIView()
let pgc = UIPageControl()
let greenLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
pgcHolder.translatesAutoresizingMaskIntoConstraints = false
pgc.translatesAutoresizingMaskIntoConstraints = false
greenLabel.translatesAutoresizingMaskIntoConstraints = false
pgcHolder.addSubview(pgc)
view.addSubview(pgcHolder)
view.addSubview(greenLabel)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// center page control in its "holder" view
pgc.centerXAnchor.constraint(equalTo: pgcHolder.centerXAnchor),
pgc.centerYAnchor.constraint(equalTo: pgcHolder.centerYAnchor),
// constrain holder view leading to view + 20, centerY
pgcHolder.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
pgcHolder.centerYAnchor.constraint(equalTo: g.centerYAnchor, constant: 0.0),
// constrain holder view WIDTH to page control HEIGHT
pgcHolder.widthAnchor.constraint(equalTo: pgc.heightAnchor),
// constrain holder view HEIGHT to page control WIDTH
pgcHolder.heightAnchor.constraint(equalTo: pgc.widthAnchor),
// constrain greenLabel Leading to holder view trailing + 8 and centerY, safe area trailing -8
greenLabel.leadingAnchor.constraint(equalTo: pgcHolder.trailingAnchor, constant: 8.0),
greenLabel.centerYAnchor.constraint(equalTo: pgcHolder.centerYAnchor),
greenLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
])
let angle = CGFloat.pi/2
pgc.transform = CGAffineTransform(rotationAngle: angle)
pgcHolder.backgroundColor = .systemRed
pgc.backgroundColor = .systemBlue
greenLabel.backgroundColor = .green
pgc.numberOfPages = 8
greenLabel.numberOfLines = 0
greenLabel.text = "UIPageControl indicates the number of open pages in an application by displaying a dot for each open page. The dot that corresponds to the currently viewed page is highlighted. UIPageControl supports navigation by sending the delegate an event when a user taps to the right or to the left of the currently highlighted dot."
}
}
Now we have our desired output:

#DonMag Answer with storyboard
Step 1 :
In View Controller, Drag UIVIew --> Name it (holderView)
Step 2 :
Drag Page Control in the holderView
Step 4:
Select the holder View - add Center Y Constraint , Trailing Constraint with Super View
Step 5:
Select the page Control View - add Center X , Center Y constraint
Step 6 :
From the View List on left panel , select both parent view and page control view , add Equal With and Equal Height constraint
Step 7:
Now select the Equal Width constraint and from left panel (properties of constraint) update superView Width to SuperView Height ,
Same for Height Constraint
Your Constraint should look like this

Related

Programatically creating constraints in a view isn't account for navigation controller despite using safeAreaLayoutGuide

I have created a UINavigationController class which allows users to Log out and displays the title of the app. I then added a UITabController as its only viewController in its viewControllers array:
let homeController = HomeController()
viewControllers = [homeController]
This UITabController (HomeController()) is then populated with a few UIViewControllers - one of which will display a Profile page. This is my first project in which I won't be using the storyboard so things have been a great challenge!
I have created a UIImageView object within the class and within my viewDidLoad for my profile page, I have used:self.view.addSubview(imageView)to add to view and then:imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true in an attempt to anchor the image to the bottom of the UINavigationController bar at the top of the screen.However the result places the image at the very top of the screen, as if the Navigation Bar isn't recognised as being visible. I read in this article: https://medium.com/#amlcurran/a-quick-guide-to-laying-out-views-in-ios-471e92deb74, that '.topLayoutGuide.bottomAnchor' represents the bottom of the navigation bar, but this has now been depreciated to my example above.
Does anyone have any ideas as to what is wrong?And also any good resources for me to fully understand programmatically constraining my elements!Thanks all!!
https://gist.github.com/JoeMcGeever/a5ce3be94fc49a8f27b1a2867bd9495b
That link shows some of the code so far - I am aware the other elements are also pinned to the top; I am just trying to fix this error regarding the navigation bar first.
Image showing what the view displays at the moment
You should really go through several auto-layout tutorials. There are many, many of them out there. After you've worked through a dozen or so, you should have a good idea of what needs to be done.
In the meantime, here is your ProfileViewController edited to give you an idea of what you were doing wrong:
class ProfileViewController : UIViewController {
let imageView : UIImageView = { //creates an image view with the name "imageView"
let image = UIImage(named: "logo")
let imageView = UIImageView(image: image)
return imageView
}()
let usernameLabel = UILabel()
// if you're addint a target referring to "self", this must be a lazy var
lazy var editProfileButton : UIButton = {
let editButton = UIButton()
editButton.backgroundColor = .orange
editButton.setTitle("Edit Profile", for: .normal)
editButton.setTitleColor(.white, for: .normal)
editButton.addTarget(self, action: #selector(handleEdit), for: .touchUpInside)
return editButton
}()
#objc func handleEdit(){
//edit handle button
print("Edit profile")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
self.title = "Profile"
usernameLabel.text = "Username Here"
// we're going to use auto-layout
[imageView, usernameLabel, editProfileButton].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
self.view.addSubview(imageView)
self.view.addSubview(usernameLabel)
self.view.addSubview(editProfileButton)
// need FULL sets of constraints, not just TOP anchors
// respect safe-area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// image view at upper-left
// image view 8-pts from top of safe area
imageView.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
// and 8-pts from left
imageView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
// give it a width of, for example, one-quarter the view width
imageView.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.25),
// give it a 1:1 ratio (square)
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor),
// button at upper-right
editProfileButton.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
editProfileButton.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
// no width or height... let the button size itself
// label below the image view
usernameLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 8.0),
usernameLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
// no width or height... let the label size itself
])
// give the name label a background color so we can see its frame
usernameLabel.backgroundColor = .cyan
}
}
Review the comments in the code to understand what I did.
Result will look about like this (I used a random image for the logo):
If you want to anchor your imageView to the top of safeArea, you have to constraint it to the topAnchor like this:
imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true

Unable to detect tap in UIScrollView inside another UIScrollView

I have a vertical scrolling UIScrollView which spans the entire ViewController, which contains this ContentView (UIView), so I am able to detect touch event outside of the scrollViews bounds:
import UIKit
class ContentView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let reversedSubviews = subviews.reversed()
let hitSubview = reversedSubviews
.first(where: { $0.hitTest(convert(point, to: $0), with: event) != nil })
if hitSubview != nil {
return hitSubview
}
return super.hitTest(point, with: event)
}
}
This is the View hiearchey
UIScrollView (vertical scroll) //Works
|
-- ContentView(UIView)
|
--UIScrollView (horizontal scroll) //Works
|
-- UIView
|
--UIButton //This does not work
|
--UIScrollView (horizontal scroll) //Works
|
-- UIView
|
--UIButton //This does not work
Inside this ContentView I have several other horizontal scrolling UIScrollViews. But I am not able to detect touch events inside these scrollViews, because of the ContentView.
How should I approach this to be able to detect touch event in all scrollViews, even if subviews are out of bounds?
It does not help to put ContentView inside horizontal scrollViews..
Here's a pretty basic example.
Following your hierarchy:
Main View
|
-- UIScrollView (vertical scroll) - Red
|
-- ContentView(UIView) - Medium Blue
|
-- UIScrollView (horizontal scroll) - Green
|
-- UIView - Light Gray
|
-- UIButton - Blue
|
-- UIView - Light Gray
|
-- UIButton - Blue
|
-- UIScrollView (horizontal scroll) - Green
|
-- UIView - Light Gray
|
-- UIButton - Blue
|
-- UIView - Light Gray
|
-- UIButton - Blue
|
This is how it looks - buttons can be tapped:
and, after scrolling down and scrolling each horizontal scroll view to the right:
Example code... no #IBOutlet or #IBAction connections needed, just assign a view controller's class to MultiScrollViewController:
class MultiScrollViewController: UIViewController {
let vertScrollView = UIScrollView()
let contentView = UIView()
let hScrollA = UIScrollView()
let hScrollB = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
[vertScrollView, contentView, hScrollA, hScrollB].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
contentView.addSubview(hScrollA)
contentView.addSubview(hScrollB)
vertScrollView.addSubview(contentView)
view.addSubview(vertScrollView)
let g = view.safeAreaLayoutGuide
let svContentG = vertScrollView.contentLayoutGuide
let svFrameG = vertScrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// vertical scroll view with 20-pts on each side for "padding"
vertScrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
vertScrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
vertScrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
vertScrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
// content view Top / Leading / Bottom 12-pts to content layout guide
contentView.topAnchor.constraint(equalTo: svContentG.topAnchor, constant: 12.0),
contentView.leadingAnchor.constraint(equalTo: svContentG.leadingAnchor, constant: 12.0),
contentView.bottomAnchor.constraint(equalTo: svContentG.bottomAnchor, constant: -12.0),
// content view Trailing to content layout guide
contentView.trailingAnchor.constraint(equalTo: svContentG.trailingAnchor, constant: 0.0),
// content view Width: scroll frame width minus 24-pts (we don't want horizontal scrolling)
contentView.widthAnchor.constraint(equalTo: svFrameG.widthAnchor, constant: -24.0),
// horizontal scroll view A Top / Leading / Trailing 8-pts to contentView
hScrollA.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8.0),
hScrollA.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0),
hScrollA.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0),
// horizontal scroll view B Top 20-pts from Bottom of hScrollA
hScrollB.topAnchor.constraint(equalTo: hScrollA.bottomAnchor, constant: 20.0),
// horizontal scroll view B Leading / Trailing / Bottom 8-pts to contentView
hScrollB.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0),
hScrollB.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0),
hScrollB.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8.0),
// so we have some vertical scrolling, make each horizontal scroll view
// 60% the height of the vertical scroll view frame
hScrollA.heightAnchor.constraint(equalTo: svFrameG.heightAnchor, multiplier: 0.6),
hScrollB.heightAnchor.constraint(equalTo: hScrollA.heightAnchor),
])
// each horizontal scroll view will have
// two UIViews, each with a UIButton
let btn1inA = UIButton()
let btn2inA = UIButton()
let btn1inB = UIButton()
let btn2inB = UIButton()
let btn1inAView = UIView()
let btn2inAView = UIView()
let btn1inBView = UIView()
let btn2inBView = UIView()
let btnViews = [btn1inAView, btn2inAView, btn1inBView, btn2inBView]
let btns = [btn1inA, btn2inA, btn1inB, btn2inB]
let ttls = ["Button 1 in A", "Button 2 in A", "Button 1 in B", "Button 2 in B"]
for (btn, str) in zip(btns, ttls) {
btn.setTitle(str, for: [])
btn.setTitleColor(.white, for: .normal)
btn.setTitleColor(.gray, for: .highlighted)
btn.titleLabel?.font = .boldSystemFont(ofSize: 18)
btn.backgroundColor = .blue
btn.contentEdgeInsets = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16)
btn.translatesAutoresizingMaskIntoConstraints = false
btn.addTarget(self, action: #selector(self.btnTap(_:)), for: .touchUpInside)
}
for (btn, v) in zip(btns, btnViews) {
v.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
v.translatesAutoresizingMaskIntoConstraints = false
v.addSubview(btn)
// center each button in a view with 8-pts padding on all 4 sides
NSLayoutConstraint.activate([
btn.topAnchor.constraint(equalTo: v.topAnchor, constant: 8.0),
btn.leadingAnchor.constraint(equalTo: v.leadingAnchor, constant: 8.0),
btn.trailingAnchor.constraint(equalTo: v.trailingAnchor, constant: -8.0),
btn.bottomAnchor.constraint(equalTo: v.bottomAnchor, constant: -8.0),
])
}
// add button-holding-views to horizontal scroll views
hScrollA.addSubview(btn1inAView)
hScrollA.addSubview(btn2inAView)
hScrollB.addSubview(btn1inBView)
hScrollB.addSubview(btn2inBView)
let svAContentG = hScrollA.contentLayoutGuide
let svAFrameG = hScrollA.frameLayoutGuide
let svBContentG = hScrollB.contentLayoutGuide
let svBFrameG = hScrollB.frameLayoutGuide
NSLayoutConstraint.activate([
// button 1 in hScroll A
// top == content top + 20
btn1inAView.topAnchor.constraint(equalTo: svAContentG.topAnchor, constant: 20.0),
// leading == content leading + 20
btn1inAView.leadingAnchor.constraint(equalTo: svAContentG.leadingAnchor, constant: 20.0),
// button 2 in hScroll A
// leading == button 1 leading + 600 (so we get horizontal scrolling
btn2inAView.leadingAnchor.constraint(equalTo: btn1inAView.trailingAnchor, constant: 600.0),
// trailing == content trailing - 20
btn2inAView.trailingAnchor.constraint(equalTo: svAContentG.trailingAnchor, constant: -20.0),
// bottom to content bottom
btn2inAView.bottomAnchor.constraint(equalTo: svAContentG.bottomAnchor),
// bottom 20-pts from hScroll A frame bottom (so it's at lower-right)
btn2inAView.bottomAnchor.constraint(equalTo: svAFrameG.bottomAnchor, constant: -20.0),
// button 1 in hScroll B
// top == content top + 20
btn1inBView.topAnchor.constraint(equalTo: svBContentG.topAnchor, constant: 20.0),
// leading == content leading + 20
btn1inBView.leadingAnchor.constraint(equalTo: svBContentG.leadingAnchor, constant: 20.0),
// button 2 in hScroll A
// leading == button 1 leading + 600 (so we get horizontal scrolling
btn2inBView.leadingAnchor.constraint(equalTo: btn1inBView.trailingAnchor, constant: 600.0),
// trailing == content trailing - 20
btn2inBView.trailingAnchor.constraint(equalTo: svBContentG.trailingAnchor, constant: -20.0),
// bottom to content bottom
btn2inBView.bottomAnchor.constraint(equalTo: svBContentG.bottomAnchor),
// bottom 20-pts from hScroll B frame bottom (so it's at lower-right)
btn2inBView.bottomAnchor.constraint(equalTo: svBFrameG.bottomAnchor, constant: -20.0),
])
// some background colors so we can see the frames
view.backgroundColor = .yellow
vertScrollView.backgroundColor = .red
// medium blue
contentView.backgroundColor = UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1.0)
hScrollA.backgroundColor = .green
hScrollB.backgroundColor = .green
}
#objc func btnTap(_ sender: UIButton) -> Void {
print(sender.currentTitle ?? "No Button Title")
}
}
I also faced this issue when I did not had much understanding about UITableView & UICollectionView and how to avoid nesting of two scroll view where scrolling are in same direction (horizontal & horizontal OR vertical & vertical).
This problem happens generally when your internal UI elements are drawn outside of the available view port(the frame which is rendered on screen) of any screen view. Sometimes it happens due to issues with constraints set up.
How to debug?
To quickly debug, identify the parent UIViews of the buttons which are not responding. Check Clips to Bounds from Identity Inspector which is false by default. Which Clips to Bounds true, you are only seeing the views which you can interact with.
You can check the issue in the below YouTube Video:
https://youtu.be/KMxFu85sXnc
Solution:
Revisit the constraints or frame calculations for the parent views.
UICollectionView (horizontal scroll) inside another UICollectionView or UITableView(vertical scroll) might be a good approach if you are not following that.

Layout problems after replacing UILabel with UITextView in a UITableViewCell

I've got basic chat functionality as part of an App I'm building. It is basically a UITable View where the UITableViewCell only contains a UILabel (the chat message text) and a UIView (serving as a speech bubble, surrounding the text. Here's the code:
class ChatMessageViewCellController: UITableViewCell {
var ChatMessageText = UILabel()
var ChatBubble = UIView()
var leadingConstraint: NSLayoutConstraint!
var trailingConstraint: NSLayoutConstraint!
var isIncoming: Bool! {
didSet {
if self.isIncoming {
self.ChatBubble.backgroundColor = UIColor(named: "customGrey")
self.leadingConstraint.isActive = true
self.trailingConstraint.isActive = false
} else {
self.ChatBubble.backgroundColor = UIColor(named: "customGreen")
self.leadingConstraint.isActive = false
self.trailingConstraint.isActive = true
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
addSubview(ChatBubble)
addSubview(ChatMessageText)
self.ChatBubble.translatesAutoresizingMaskIntoConstraints = false
self.ChatMessageText.translatesAutoresizingMaskIntoConstraints = false
self.ChatBubble.backgroundColor = UIColor(named: "customGreen")
self.ChatBubble.layer.cornerRadius = 10
self.ChatMessageText.numberOfLines = 0
self.ChatMessageText.textColor = .white
self.ChatMessageText.font = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.light)
let constraints = [
self.ChatMessageText.topAnchor.constraint(equalTo: topAnchor, constant: 16),
self.ChatMessageText.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -32),
self.ChatMessageText.widthAnchor.constraint(lessThanOrEqualToConstant: 220),
self.ChatBubble.topAnchor.constraint(equalTo: ChatMessageText.topAnchor, constant: -16),
self.ChatBubble.trailingAnchor.constraint(equalTo: ChatMessageText.trailingAnchor, constant: 16),
self.ChatBubble.bottomAnchor.constraint(equalTo: ChatMessageText.bottomAnchor, constant: 16),
self.ChatBubble.leadingAnchor.constraint(equalTo: ChatMessageText.leadingAnchor, constant: -16),
]
NSLayoutConstraint.activate(constraints)
self.leadingConstraint = self.ChatMessageText.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 32)
self.trailingConstraint = self.ChatMessageText.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -32)
}
My problem is this:
I'm not feeding the UILabel with standard strings but with NSAttributedStrings, as I'd like to get some of the links in there clickable and parts of the text selectable by the user.
So I've been told to use a UITextView instead of the UILabel. I've thus made the following 2 changes:
Changed var ChatMessageText = UILabel()to var ChatMessageText = UITextView()
Did remove self.ChatMessageText.numberOfLines = 0 as UITextView doesn't have a numberOfLines member
Xcode doesn't complain and the app compiles and runs but it completely messes with my layout and I just can't figure out why. All the constraints from the UILabel should also work for the UITextView - at least I thought so. But here's how the screen looks like.
What am I doing wrong? Do I need to add / alter constraints?
By default, a UITextView has scrolling enabled.
While this seems obvious, that allows the user to enter more lines of text than will fit in the frame, and the user can scroll the text up and down.
In order for this to happen, UIKit has to know the frame of the text view. If the frame is not set, UIKit has no way to know how many lines to display, or how wide the view should be. So unless we have given the text view a full set of constraints, auto-layout will give it a size of .zero. Even if given a width (or max width) constraint, auto-layout still doesn't know how many scrollable lines of text we want displayed.
Setting .isScrollEnabled = false on the text view changes all of that.
Now, if we only constrained the position and width of the text view, UIKit will calculate the height based on the content size of the .text property.
This can be easily demonstrated. We'll create two text views, give them each top, leading and max-width (lessThanOrEqualTo) constraints, and the same text... but set .isScrollEnabled = false on one of them:
class TextViewTestViewController: UIViewController {
let nonScrollingTextView = UITextView()
let scrollingTextView = UITextView()
override func viewDidLoad() {
super.viewDidLoad()
let s = "This is a test string to demonstrate UITextView size behavior."
[nonScrollingTextView, scrollingTextView].forEach {
tv in
tv.translatesAutoresizingMaskIntoConstraints = false
tv.font = UIFont.systemFont(ofSize: 17.0)
tv.text = s
view.addSubview(tv)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
nonScrollingTextView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
nonScrollingTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
nonScrollingTextView.widthAnchor.constraint(lessThanOrEqualToConstant: 300.0),
scrollingTextView.topAnchor.constraint(equalTo: nonScrollingTextView.bottomAnchor, constant: 40.0),
scrollingTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
scrollingTextView.widthAnchor.constraint(lessThanOrEqualToConstant: 300.0),
])
// disable scrolling on the "top" text view
nonScrollingTextView.isScrollEnabled = false
// top text view is cyan
nonScrollingTextView.backgroundColor = .cyan
// bottom text view is green (although we won't see it)
scrollingTextView.backgroundColor = .green
}
}
Result:
We've added two text views, but only disabled scrolling on the "top" one (cyan background). We don't even see the second one (green background), because auto-layout gives it a height of Zero.
Worth noting... if the text view has scrolling disabled and has editing enabled, it will automatically grow / shrink as the user adds / deletes text.

scale image to size with aspectFit

I am building a app where you can drag images inside a view. Currently it look like this:
As you can see I marked the background color from the image view in green. The image view has contentMode aspectFit and it is 40 pixels smaller than the black view behind it.
I want the that the image is the full length and width of the image view. The contentMode should be aspectFit, that nothing is cut away from the image. Is it possible to resize the image, that it has 20 pixels or a bit more space from the view?
#adri567 You should use UIViewContentModeScaleToFill property like
imageView.contentMode = .scaleToFill
Try with this!
If you want to keep the image at the same size, but don't want to stretched it. your solution is something else.
Display your image as .aspectFit as in the question
the green view that you display replace it with the same image in .aspectFill but blur it as much as it looks good.
Simple math can solve this.
for shortcuts: H -> height , W -> Width
We know that general formula for this is: h1 / w1 = h2 / w2
Hscreen / Wscreen = Himage / Wimage
so we know screen width, image height and image width.
we can get screen width as -> view.frame.width
also we can get image size as -> image.size.width and image.size.height
Hscreen = (Himage) * (WScreen) / Wimage
..
you can use Hscreen to imageViews height anchor.
One approach...
embed a UIImageView in a (green) UIView
constrain the imageView on all 4 sides + 20-pts "padding"
constrain the width of the greenView (or its leading and trailing)
constrain the Y position of the greenView (top or centerY)
constrain the height of the imageView with a multiplier based on the image width and height
Here is a simple example:
class ViewController: UIViewController {
let imgView: UIImageView = {
let v = UIImageView()
v.contentMode = .scaleToFill
return v
}()
let greenView: UIView = {
let v = UIView()
v.backgroundColor = .green
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
// replace with your image name
guard let img = UIImage(named: "bkg640x360") else {
fatalError("Could not load image!")
}
view.backgroundColor = .black
// set the imgView's image
imgView.image = img
// use auto-layout constraints
imgView.translatesAutoresizingMaskIntoConstraints = false
greenView.translatesAutoresizingMaskIntoConstraints = false
// add imgView to greenview
greenView.addSubview(imgView)
// add greenView to self.view
view.addSubview(greenView)
// we want to respect safe-area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain greenView leading and trailing to view (safeArea)
greenView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
greenView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
// constrain greenView centerY to view centerY
greenView.centerYAnchor.constraint(equalTo: g.centerYAnchor, constant: 0.0),
// constrain imgView to all 4 sides of greenView with 20-pts "padding"
imgView.topAnchor.constraint(equalTo: greenView.topAnchor, constant: 20.0),
imgView.bottomAnchor.constraint(equalTo: greenView.bottomAnchor, constant: -20.0),
imgView.leadingAnchor.constraint(equalTo: greenView.leadingAnchor, constant: 20.0),
imgView.trailingAnchor.constraint(equalTo: greenView.trailingAnchor, constant: -20.0),
// constrain imgView proportional height equal to image height / width
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor, multiplier: img.size.height / img.size.width),
])
}
}
The result, using a 640 x 360 image:
and using a 512 x 512 (square) image:
These are my source images:

Adding Stackview to UIScrollView

I have a UIScrollView. It has a stack view. And this stack view contains 12 buttons. (Horizontal scroll view)
Stackview constraints :- top,leading,trailing,bottom to the scroll view and equal widths to the scroll view.
My problem is every time when I run, stack view width limits to the scroll view width and buttons are too small acording to the width of the stack view and my scroll view is not scrollable.
How to make this scrollable
Step-by-Step for setting this up in IB / Storyboards...
Add a view - height 50 leading/top/trailing - blue background
add a scrollview to that view - pin leading/top/trailing/bottom to 0 - set scrollview background to yellow so we can see where it is
add a button to the scroll view
duplicate it so you have 12 buttons
group them into a stack view, and set the stack view's constraints to 0 leading/top/trailing/bottom
and set the stack view's distribution to "equal spacing"
result running in simulator (with no code at all):
and the buttons scroll left and right... no code setting of .contentSize...
So you want this:
Here's how I did it in Xcode 8.3.3.
New Project > iOS > Single View Application.
Open Main.storyboard.
Drag a scroll view into the scene.
Pin top, leading, and trailing of the scroll view to 0. Set height to 30.
Drag a horizontal stack view into the scroll view.
Pin all four edges of the stack view to 0.
Set stack view spacing to 4.
Drag twelve buttons into the stack view.
Set target device to iPhone SE.
Build & run.
Resulting document outline:
If you make your Stackview width equal to the scrollview width, then that's all you'll get, and of course it won't scroll.
Don't give your Stackview a width constraint... let the buttons "fill it out".
Edit: Here is a simple example that you can run directly in a Playground page:
import UIKit
import PlaygroundSupport
class TestViewController : UIViewController {
let scrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
return v
}()
let stackView : UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .horizontal
v.distribution = .equalSpacing
v.spacing = 10.0
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 the stack view to the scroll view
scrollView.addSubview(stackView)
// constrain the stackview view to 8-pts on each side
// this *also* controls the .contentSize of the scrollview
stackView.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 8.0).isActive = true
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 8.0).isActive = true
stackView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: -8.0).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -8.0).isActive = true
// add ten buttons to the stack view
for i in 1...10 {
let b = UIButton()
b.translatesAutoresizingMaskIntoConstraints = false
b.setTitle("Button \(i)", for: .normal)
b.backgroundColor = .blue
stackView.addArrangedSubview(b)
}
}
}
let vc = TestViewController()
vc.view.backgroundColor = .yellow
PlaygroundPage.current.liveView = vc

Resources