I'm trying to make buttons with titles that would change their width, height and font size of the title text depending on iOS device (autoscaling). I'm using Xcode 10 coding Swift.
Here is the simple example: I created in IB red button that covers the whole screen (top, bottom, leading and trailing constraints are set to 0) with the title "Button" in the center with the font size set to 350. The title would fit width of 12.9" iPad perfectly (see the pic).
I entered FitToWidth code like this:
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.minimumScaleFactor = 0.1
}
When I run the code on device with lower screen resolution than iPad 12.9" I see that the button title is scaled and fits the screen width but it is shifted down (see the pics).
iPad 12.9 simulation
iPhone SE simulation
What am I missing?
I need title text to be situated in the center of the button as I did it in IB. I tried various constraints applied to the button but nothing worked out.
It looks like I found a solution.
I just added such string to my code in viewDidLoad:
button.titleLabel?.baselineAdjustment = UIBaselineAdjustment.alignCenters
Now the title is in the middle of the button for any iOS device:
Resize the button view to make title fit in each screen size
Try this in viewDidAppear()
button.sizeToFit()
Related
I am trying to figure out how to make my three buttons in a stackview have relative sizing and keep the same multiplier spacing on different devices, e.g. the buttons will be bigger if i am on an ipad compared to an iphone. Secondly the spacing between left and right edges of buttons will be bigger on an ipad compared to that of an iphone device. So far I currently have three buttons in a stackview. I have added horizontally and vertical allignment to my stackview. I have played around with adding equal heights and width and changed the multiplier as well as adding constraints to the buttons however it did not get my desired result.
Here is a screenshot of how i'd like my items to be placed on all devices:
UIStackView has only static spacing. You have two options:
Change spacing inside code with viewWillLayoutSubviews. I prefer it over viewDidLayoutSubviews because changes will follow rotation animation. But if your calculations will depend on other subviews(not just self.view), these frames will not be updated yet. You can change constraint.constant like this too.
#IBOutlet var stackView: UIStackView!
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
stackView.spacing = view.bounds.height * 0.05
}
Using only storyboard, you can't add spacing constraint with modifier. But you can add a transparent view which will be your spacer, and apply width/height modifier to it. This can be used both for UIStackView and for plain views.
I prefer adding equal height constraint for all views with same size(like btn1.height = btn2.height, btn1.height = btn3.height), so I can set size with one constraint to all of them(btn1.height = superview.height * 0.1).
Something like this should work for your:
Result:
I am creating a sample application in which i am copying Facebook screens in order to practice auto layouts.
When I run login screen in portrait mode, it looks perfect.
The problem is as soon as the orientation changes to landscape, all the views collapse because of header image, as shown here
What i want is that, in landscape mode, header image disappears so that other views get its space. I don't want to use scrollview.
I tried this:
headerImageView.isHidden = true
But the result came out to be this
The imageview got disappeared but didn't leave its space.
Can anyone suggest me a good solution?
P.s
Sorry for the images being this way because of my reputation.
When using Auto Layout you can leverage Size Classes.
See description below or example here: https://github.com/jonaszmclaren/AutolayoutExample
Set image view for compact width and height (wC hC - iPhone in landscape) and for wR hC (iPhone Plus in landscape) to not installed:
Constraint between text field and image view not enabled for wC hC and wR hC:
And finally for wC hC and wR hC you have to define text fields's top contraint - I did it to the top of the view.
This way, image view for portait will be visible and text view pinned to image view, and in landscape image view will be hidden and text field pinned to top of the view.
The best way is to use scrollView in such type of scenarios. If you don't want to have the scrollView, then you must give the bottom constraint for last button, and set the priority low of that particular constraint. It will work fine for current screen(both landscape and portrait), but when you'll go for small screen i.e 4s or 5, then purpose of auto layout will fail.
If you hide the image than it will only not show to user But Space will be used by Image on screen. Better Approach is you can set the Height Of Image 0 when orientation change to Landscape. You can create the Outlet of Height Constraint of Image and Change it according to Orientation.This method is called before orintation change. You need to Create outlat of Height constraint of Image.
#IBOutlet var heightConstraint : NSLayoutConstraint!
override func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval)
{
if toInterfaceOrientation == .landscapeLeft || toInterfaceOrientation == .landscapeRight{
// Imageview height constraint outlate
heightConstraint.constant = 0
}
else{
heightConstraint.constant = 100
}
}
isHidden will just changed the visibility of the view. It will not remove it from that position. To solve this issue create a outlet of height constraint of header view and changed it to 0 on orientation change.
ex:
headerViewHeightConstraint.constant = 0.0
self.view.layoutIfNeeded()
and to restore it on portrait mode set height again.
headerViewHeightConstraint.constant = // height value which you want to set
self.view.layoutIfNeeded()
Another option could be to place your view inside a stack view. Then hiding the headerImageView should recover the unused space.
EDIT: I post a screenshot of what I would like to implement in iPad and iPhone. When using the button in iPhone, the UITableView opens and has to cover a part of the UITextView.
I have a universal project for iPhone and iPad. In the main storyboard there are two controls, an UITextField and a UITableView. The UITextField keeps the left 1/3 portion of the screen and the UITableView the remaining part of the screen.
When in iPhone, I hide the UITableView and I would like that the UITextView keeps all the screen. Then, when the user taps on a button, the UITableView appears over the text field (I dismiss the UITableView as soon as the user selects a choice).
How can I have the tableView to enlarge in a way that its right border, go to the right border of the screen?
One way to implement what you described is to start with the TableView "outside" of the scene, with a negative Leading Space to the main view. Them when users press the button, the value of the Leading Space Constraint is set to zero.
Example:
Final result:
ViewController code:
class ViewController: UIViewController {
#IBOutlet weak var tableViewLeadingMargin: NSLayoutConstraint!
#IBAction func revealTableView(sender: UIButton) {
UIView.animateWithDuration(
3.0,
animations: {
self.tableViewLeadingMargin.constant = 0
self.view.layoutIfNeeded()
}
)
}
}
There's just one caveat: you'll need to adjust the width of the TableView programatically, based on the iPhone screen size.
The answer above the iPhone issue. For iPad, I'd suggest using Size Classes, creating a specific layout/constraints for them:
To illustrate:
I have a UITableViewCell with three labels on it. Two along the top and one that holds some content along the bottom. I constrain the two top labels so that they are 8pts from the top of the view. The left label is 8pts from the leading edge and the right label is 8pts from the trailing edge. I then set the left label to be 12pts minimum from the right label.
Title constraints
Date constraints
It looks fine in Xcode when I evaluate it. I can add a really long title (left topmost label) and it truncates the text correctly, giving me my 12pt margin to the date label.
When I run the app at runtime, the constraints don't seem to be applied. The title label is the full width of the device with the date label no where to be seen.
It does this on the iPhone 5, 5s, 6 and 6 Plus simulators. What am I doing wrong? In my viewDidLoad() method, I am loading the nib containing the UITableViewCell and then registering it. I also add a button to the UINavigationController.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.leftBarButtonItem = self.editButtonItem()
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = controllers[controllers.count-1].topViewController as? DetailViewController
}
// Register our additional nibs
let nibName = UINib(nibName: "StandardNoteCell", bundle: nil)
tableView.registerNib(nibName, forCellReuseIdentifier: identifier)
self.newNoteButton = self.createNewRoundButton()
self.navigationController?.view.addSubview(self.newNoteButton)
}
Does anything stand out as being incorrect?
The problem is that you have two text labels with ambiguous information about how to resolve the case in which they can't both fully fit in the horizontal space available.
To resolve, set the date label's compression resistance to "1000 (Required)". This way the date will always be visible no matter what, and the other views (the title label, in this case) will work around that by shrinking.
As an experiment, try setting both labels' compression resistance to 1000. This will be impossible to achieve, and you'll see errors in your console. So use required constraints sparingly - you want your constraints to be as flexible as possible.
XCode does not know the size of UILabel, you can see your constrains are all red in the horizontal axis.
There is two solutions for your problem
Set the width of the labels
Set the hugging/Compression of the label
If you set the width the label won't resize depending on the size of the screen.
If you set the hugging it will expand/contract depending on the priority of each UITextField
Content Hugging: Don't want to grow
Content Compression Resistance: Don't want to shrink
Just a wild guess but is your date label actually being set with any text? Because if not, it would explain the behaviour you're observing.
I have a universal app which i used the story board to constrain a button so that from the iPhone 4 to the iPhone 6plus, the size will be well proportioned to the device's screen. The title of the button is the number "2" (All that is shown on the screen is the number "2" which can be pressed). For some reason I cannot figure out how to auto resize the font with the button size so that the number looks proportioned to the screen as intended.
Ideas?
Use Interface Builder only. You can add an UILabel as the same size as your button. Remove the title of Your button. Set text for the UILabel. Then change the AutoShrink property of the UILabel to "Minimum Font Scale". But you will not able to change the text color for different states.
Use code to fit width. Try this:
self.yourButton.titleLabel.adjustsFontSizeToFitWidth = YES;
self.yourButton.titleLabel.minimumScaleFactor = 0.5;
If you want to fit the height of title. Maybe you must calculate the height by yourself. Use [font lineHeight] to make sure the font fit your button.
Call this function (from e.g. viewDidAppear) for every button in your app:
func setAppropriateFontSize( butt:UIButton){
let maxFontSize = butt.frame.height
butt.titleLabel!.font = UIFont(name: "Helvetica", maxFontSize)
butt.titleLabel!.adjustsFontSizeToFitWidth = true
butt.titleLabel!.minimumScaleFactor = 0.01
}
Above, maxFontSize is the maximum font-size for which the text still fits vertically within the button. This font is probably too large, and therefore we allow it to shrink (adjustsFontSizeToFitWidth = true means that we allow the font to shrink, as it will never increase to fit the width).