I have code for my collectionView that adjusts the content so that it sits beneath the navigation bar.
collectionView.contentInsetAdjustmentBehavior = .never
let tabBarHeight = self.tabBarController?.tabBar.bounds.height
let navBarHeight = self.navigationController?.navigationBar.bounds.height
self.edgesForExtendedLayout = UIRectEdge.all
self.collectionView.contentInset = UIEdgeInsets(top: navBarHeight!, left: 0.0, bottom: tabBarHeight!, right: 0.0)
This works well on every other device on iOS 11 except for iPhone X, on iPhone X, the content sits behind the nav bar and toolbar on app start.
Is there something I am missing for iPhone X specifically?
Thanks
I think you forgot to calculate the height of status bar.
Before iPhone X, the height of status bar is 20pt and in iPhoneX it is 44pt. That's the reason you can not see the complete cell.
To do that, add your constraints from superview and write the following code:
cv.contentInsetAdjustmentBehavior = .never
let tabBarHeight = self.tabBarController?.tabBar.bounds.height ?? 0
let statuBarHeight = UIApplication.shared.statusBarFrame.height
let navBarHeight = self.navigationController?.navigationBar.bounds.height ?? 0
self.edgesForExtendedLayout = UIRectEdge.all
cv.contentInset = UIEdgeInsets(top: navBarHeight+statuBarHeight, left: 0.0, bottom: tabBarHeight, right: 0.0)
Hope this helps :)
Related
I've following code to adjust scroll view inset. The content inside scroll view goes below the keyboard limit as shown in screen recording - https://imgur.com/a/XWfiVYX
How do I fix this so that bottom of bottomView stick to the top of keyboard and doesn't scroll up or down?
#objc func keyboardWillShow(notification:NSNotification) {
let userInfo = notification.userInfo!
let keyboardFrame:CGRect = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let kbSize = keyboardFrame.size
let contentInset:UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: kbSize.height, right: 0.0)
scrollView.contentInset = contentInset
scrollView.scrollIndicatorInsets = contentInset
}
Using scrollView.contentInset will not fix you view above kb, it will scroll up and down as you are seeing it in your case. This solution is from Apple's code which is good to show the textView up above kb only and doesn't stick the bottom of text view above kb.
You need to move up the entire self.view by kb height.
I'm trying to make used of preservesSuperviewLayoutMargins through a nested view hierarchy but hitting a lot of issues with UIKit and wondering what I am doing wrong (or whether this is a genuine bug).
I'm trying to lay out a handful of views (some by the side of each other, some above each other) like so:
// Various combinations here will create different visual results (it will either work or won't)
let i1 = ImageView()
//let i1 = LabelView()
let i2 = ImageView()
//let i2 = LabelView()
let i3 = ImageView()
//let i3 = LabelView()
let view = LeftRight(left: i1, right: TopBottom(top: i2, bottom: i3))
//let view = LeftRight(left: TopBottom(top: i2, bottom: i3), right: i1)
With some layout's I can get it to work, shown below. You can see that the images on the right are correctly indented against the green view, which is in turn correctly indented against the white view.
But with others I am struggling:
Running code similar to this in an app I can get a complete lockup and memory runaway.
The indenting is done using layoutMargins like this:
let masterView = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
masterView.translatesAutoresizingMaskIntoConstraints = false
masterView.layoutMargins = UIEdgeInsets(top: 5.0, left: 5.0, bottom: 5.0, right: 5.0)
let subView = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
subView.translatesAutoresizingMaskIntoConstraints = false
subView.layoutMargins = UIEdgeInsets(top: 5.0, left: 5.0, bottom: 5.0, right: 5.0)
Here's a sample of the image layout from a playground. The full playground project can be found here and can easily be loaded up and ran in XCode to see what I mean.
class ImageView: UIView {
init() {
super.init(frame: CGRect(x: 0, y: 0, width: 1000, height: 1000))
self.preservesSuperviewLayoutMargins = true
self.layoutMargins = .zero
let imageView = UIImageView(image: UIImage(named: "jesus"))
imageView.contentMode = .center
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.clipsToBounds = true
self.clipsToBounds = true
imageView.backgroundColor = UIColor.red
self.addSubview(imageView)
imageView.leftAnchor.constraint(equalTo: self.layoutMarginsGuide.leftAnchor).isActive = true
imageView.rightAnchor.constraint(equalTo: self.layoutMarginsGuide.rightAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor).isActive = true
}
}
I believe that you forgot something those are required.
Implementation of intrinsicContentSize on ImageView and Label
Missing constraints that make both subviews have the same width and height in those 2 UIView subclasses (TopBottom and LeftRight)
right view of view (LeftRight) still haven't been set its translatesAutoresizingMaskIntoConstraints property to false
Add the constraint for bottom of the outmost frame view
Call layoutIfNeeded() on frame view
top.widthAnchor.constraint(equalTo: bottom.widthAnchor).isActive = true
top.heightAnchor.constraint(equalTo: bottom.heightAnchor).isActive = true
Ok. Found your issue. Your master view and your subview have both a frame of size 500x500 and a marginLayout of 5 points per edge. If you instantiate those with a frame of .zero then everything should be fine.
Update
After taking a bit more time with your Playground I also added this to ImageView:
private var imageView: UIImageView!
override var intrinsicContentSize: CGSize {
return imageView?.intrinsicContentSize ?? .zero
}
and LabelView:
private var textLabel: UILabel!
override var intrinsicContentSize: CGSize {
return textLabel?.intrinsicContentSize ?? .zero
}
Now in your init methods of both ImageView and LabelView use these instance properties instead of a local variable. After doing so it seems that all possible combinations work. If you had one specific combination that you knew it didn't please let me know so I can test it.
I am able to set the image and text for button. But it is aligned horizontally. I want the image and text to be aligned vertically inside the button. i.e., text below the image
How to make these changes using storyboard?
What I want is this:
What I am getting now is this:
If you are looking for output like this
1) Select button and go to Attribute Inspector in your storyboard to get this result of Button size 50*50
2) Now set Title Insets of Button
I've written an extension that do the works for you, Swift 4 compatible.
public extension UIButton {
func alignTextBelow(spacing: CGFloat = 6.0) {
if let image = self.imageView?.image {
let imageSize: CGSize = image.size
self.titleEdgeInsets = UIEdgeInsetsMake(spacing, -imageSize.width, -(imageSize.height), 0.0)
let labelString = NSString(string: self.titleLabel!.text!)
let titleSize = labelString.size(withAttributes: [NSAttributedStringKey.font: self.titleLabel!.font])
self.imageEdgeInsets = UIEdgeInsetsMake(-(titleSize.height + spacing), 0.0, 0.0, -titleSize.width)
}
}
}
Where spacing is the distance between image and text.
Yes you can do it from storyboard without writing any logic also.
1) Select button and go to Attribute Inspector in your storyboard.
2) Assign Image to the button. (Don't use background Image)
3) Set Title text to that button.
4) Now you need to set edge and Inset so first select image from edge and set Inset as you need and then select title from edge and set inset as per your need.
Hope this helps.
You can easily and visually achieve the alignment using the Edge property of the Button from Property Window(Attribute Inspector) you can change the inset values of Title, and Image as per your need, see image below
Hope it helps.
Cheers.
Swift 4.2 compatible code is here , you just need to call this function
public extension UIButton
{
func alignTextUnderImage(spacing: CGFloat = 6.0)
{
if let image = self.imageView?.image
{
let imageSize: CGSize = image.size
self.titleEdgeInsets = UIEdgeInsets(top: spacing, left: -imageSize.width, bottom: -(imageSize.height), right: 0.0)
let labelString = NSString(string: self.titleLabel!.text!)
let titleSize = labelString.size(withAttributes: [NSAttributedString.Key.font: self.titleLabel!.font])
self.imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing), left: 0.0, bottom: 0.0, right: -titleSize.width)
}
}
}
a refined one
func alignTextUnderImage(spacing: CGFloat = 6.0) {
guard let image = imageView?.image, let label = titleLabel,
let string = label.text else { return }
titleEdgeInsets = UIEdgeInsets(top: spacing, left: -image.size.width, bottom: -image.size.height, right: 0.0)
let titleSize = string.size(withAttributes: [NSAttributedString.Key.font: label.font])
imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing), left: 0.0, bottom: 0.0, right: -titleSize.width)
}
Setting Attribute Inspector in storyboard
Setup button size
Setup image's top
Setup title's top and left
Thank you #rbiard. Another workaround when you are developing a multilingual mobile app and when using UISemanticContentAttribute (right to left and left to right). This should work:
func alignTextBelow(spacing: CGFloat = 10.0) {
guard
let image = imageView?.image,
let label = titleLabel,
let string = label.text
else { return }
let titleSize = string.size(withAttributes: [NSAttributedString.Key.font: label.font])
//when the selected language is English
if L102Language.currentAppleLanguage() == "en" {
titleEdgeInsets = UIEdgeInsets(top: spacing, left: -image.size.width, bottom: -image.size.height, right: 0)
imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing), left: 0.0, bottom: 0.0, right: -titleSize.width)
} else {
//When selecting a right to left language. For example, Arabic
titleEdgeInsets = UIEdgeInsets(top: spacing, left: 0, bottom: -image.size.height, right: -image.size.width)
imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing), left: -titleSize.width, bottom: 0, right: 0)
}
}
You cannot change layout directly in UIButton, but you can try to use these properties on UIButton:
UIButton *button = ...
button.titleEdgeInsets = UIEdgeInsetsMake(....);
button.imageEdgeInsets = UIEdgeInsetsMake(....);
If it won't help I'd recommend to subclass from UIResponder and make your own component and do your own layout as you needed it.
I m very new to IOS development and AutoLayout .
I am facing issues to align the Image and Text inside UIbutton using Storyboard. I had tried to achieve it with TitleEdgeinset and ImageEdge insets accordingly to place the Title ( text ) vertically centered below the Image. But the issue is I have 3 similar buttons which are Vertically stacked ( StackView) and the Text is dynamically set since we have localized strings ( includes Arabic rtl ) .
The image and text moves according to the text length. Is there any ways that I can achieve to make all the buttons with image and text vertically alligned.Also, different screen resolutions are not currently working if using edge insets. Appreciate your help . Thanks in advance.
Few days ago, I solved similar problem,try this
private func adjustImageAndTitleOffsetsForButton (button: UIButton) {
let spacing: CGFloat = 6.0
let imageSize = button.imageView!.frame.size
button.titleEdgeInsets = UIEdgeInsetsMake(0, -imageSize.width, -(imageSize.height + spacing), 0)
let titleSize = button.titleLabel!.frame.size
button.imageEdgeInsets = UIEdgeInsetsMake(-(titleSize.height + spacing), 0, 0, -titleSize.width)
}
call this method for each button, like
self.adjustImageAndTitleOffsetsForButton(yourButton)
Combining Ajay's and Matej's answer:
import Foundation
import UIKit
class VerticalButton: UIButton {
override func awakeFromNib() {
super.awakeFromNib()
self.contentHorizontalAlignment = .left
}
override func layoutSubviews() {
super.layoutSubviews()
centerButtonImageAndTitle()
}
private func centerButtonImageAndTitle() {
let titleSize = self.titleLabel?.frame.size ?? .zero
let imageSize = self.imageView?.frame.size ?? .zero
let spacing: CGFloat = 6.0
self.imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing),left: 0, bottom: 0, right: -titleSize.width)
self.titleEdgeInsets = UIEdgeInsets(top: 0, left: -imageSize.width, bottom: -(imageSize.height + spacing), right: 0)
}
}
I've modified Ajay's answer because my images weren't centered:
func centerButtonImageAndTitle(button: UIButton) {
let spacing: CGFloat = 5
let titleSize = button.titleLabel!.frame.size
let imageSize = button.imageView!.frame.size
button.titleEdgeInsets = UIEdgeInsets(top: 0, left: -imageSize.width, bottom: -(imageSize.height + spacing), right: 0)
button.imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing), left: -imageSize.width/2, bottom: 0, right: -titleSize.width)
}
This is a solution that worked for me. Just set the button class to HorizontallyCenteredButton in storyboard and set the title and image top and bottom insets according to your needs (to place the image higher than title) and the button will adjust horizontal insets automatically so that the image is centered above title.
class HorizontallyCenteredButton: LocalizedButton {
override func awakeFromNib() {
super.awakeFromNib()
self.contentHorizontalAlignment = .left
}
override func layoutSubviews() {
super.layoutSubviews()
self.centerButtonImageAndTitle()
}
func centerButtonImageAndTitle() {
let size = self.bounds.size
let titleSize = self.titleLabel!.frame.size
let imageSize = self.imageView!.frame.size
self.imageEdgeInsets = UIEdgeInsets(top: self.imageEdgeInsets.top, left: size.width/2 - imageSize.width/2, bottom: self.imageEdgeInsets.bottom, right: 0)
self.titleEdgeInsets = UIEdgeInsets(top: self.titleEdgeInsets.top, left: -imageSize.width + size.width/2 - titleSize.width/2, bottom: self.titleEdgeInsets.bottom, right: 0)
}
}
To align all the buttons vertically, first select the buttons, then click the button on the bottom-right of the storyboard titled "Align", and finally select "Vertical Centers" in the menu that appears. That should do the trick.
I created a view, then put a VStack with image and text, as well as button.
Make sure the VStack constraints are 0 on all 4 sides so it covers the button :)
this is an easy one, so apologies if you have already tried it!
you don't need to set anything for insets, just make sure that the 3 buttons are aligned with horizontal centres ...
select the controls you want to align, and click on the button highlighted below (bottom right on the xCode screen) and select the Vertical Centres option to align the three with each other, or select Horizontally in Container to put them in the middle of your view.
I'm working with a UICollectionView but I have a weird behaviour.
I want a horizontal scroll, small cell height ( smaller than the UICollectionView height).
Tapping a button will increase even more the height of the collection.
The problem is that the layout of my cells is changing also.
I want the collection view height to increase with the cells staying in the same position.
Here attached 2 screen captures (before/after button touched).
Here is my code:
var smallLayout:UICollectionViewFlowLayout = UICollectionViewFlowLayout();
smallLayout.scrollDirection = .Horizontal
var itemHeight = 100
smallLayout.itemSize = CGSizeMake(150, itemHeight);
var bottomDist:CGFloat = 1
var topDist = collHeight - itemHeight - bottomDist
smallLayout.sectionInset = UIEdgeInsets(top: topDist, left: 2, bottom: bottomDist, right: 2)
smallLayout.minimumInteritemSpacing = 15
smallLayout.minimumLineSpacing = 10
So, when touching the button I have the following code:
let viewHeight = CGRectGetHeight(self.view.frame)
let viewWidth = CGRectGetWidth(self.view.frame)
var collHeight:CGFloat = 600
var collYPosition = viewHeight-CGFloat(collHeight)-20
var rect = CGRectMake(0, CGFloat(collYPosition), CGFloat(viewWidth), CGFloat(collHeight))
dishCollectionView.frame = rect
So, I cannot understand why my layout is changing.
I want to have my cell at the bottom.
Perhaps if you reset the top of your sectionInset to accommodate for the additional height, i.e. an updated topDist calculation. Try adding something like this to you on touch code:
var bottomDist:CGFloat = 1
var topDist = collHeight - itemHeight - bottomDist
dishCollectionView.collectionViewLayout.sectionInset = UIEdgeInsets(top: topDist, left: 2, bottom: bottomDist, right: 2)
dishCollectionView.collectionViewLayout.invalidateLayout()