How does one raise the position of the navigation bar's main title? - ios

I have searched the internet thoroughly and cannot find how to adjust how high up the navigationItem.title appears. I am referring to the title that shows up in the middle of the navigation bar.

Use setTitleVerticalPositionAdjustment function of UINavigationBar which will let you set an required vertical offset (can be positive or negative) for navigation bar title. Read more about it here.
Good luck :)

Tomas' answer is the simplest one. Except of that you could use a custom titleView. That way you can inset the text horizontally as well as vertically:
class TitleView: UIView {
private let label: UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 15)
return label
}()
var title: String? {
get { return label.text }
set { label.text = newValue }
}
convenience init(title: String, inset: UIEdgeInsets) {
self.init()
label.text = title
addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: topAnchor, constant: inset.top).isActive = true
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: inset.left).isActive = true
bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: inset.bottom).isActive = true
trailingAnchor.constraint(equalTo: label.trailingAnchor, constant: inset.right).isActive = true
}
}
Then you can simply use it like this:
let titleView = TitleView(title: "TitleView", inset: .init(top: -10, left: 0, bottom: 0, right: 0))
vc.navigationItem.titleView = titleView

Related

How can I add padding (and a background) to a UILabel

This has been asked before on Stackoverflow but the solutions don't work for me.
I'm building an app in SwiftUI but I need to use MapKit for maps, so I'm creating some views in UIKit (programmatically, no interface builder). My current problem is that I want to make a capsule background with some padding around a UILabel. It needs to accommodate arbitrary text, so I can't hardcode the sizes, but I can't find a way to determine the innate size of the UILabel text at runtime. I've tried UIEdgeInsets without success.
In the attached code, I am showing a SwiftUI version of what I'm trying to achieve, and then the UIKit attempt. I'd like to follow best practices, so please feel free to tell me any better ways of achieving this.
(Screenshot of what the code produces)
import SwiftUI
struct SwiftUICapsuleView: View {
var body: some View {
Text("Hello, World!")
.padding(6)
.background(Color.gray)
.cornerRadius(15)
}
}
struct UIKitCapsuleView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
let label = UILabel(frame: .zero)
label.numberOfLines = 0
label.font = UIFont.systemFont(ofSize: 17)
label.text = "Goodbye, World!"
label.layer.cornerRadius = 15
label.layer.backgroundColor = UIColor.gray.cgColor
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
label.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
label.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
return view
}
func updateUIView(_ view: UIView, context: Context) {
}
}
struct ExperimentView_Previews: PreviewProvider {
static var previews: some View {
VStack {
SwiftUICapsuleView()
UIKitCapsuleView()
}
}
}
You probably want to set the background color and rounded corners of the view, not the label.
You also should use a full set of constraints.
Give this a try:
struct UIKitCapsuleView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
// set the view's background color
view.backgroundColor = .cyan
// set the cornerRadius on the view's layer
view.layer.cornerRadius = 15
let label = UILabel(frame: .zero)
label.numberOfLines = 0
label.font = UIFont.systemFont(ofSize: 17)
label.text = "Goodbye, World!"
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
// you can adjust padding here
let padding: CGFloat = 6
// use full constraints
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: view.topAnchor, constant: padding),
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding),
label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -padding),
label.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -padding),
])
return view
}
func updateUIView(_ view: UIView, context: Context) {
}
}

Align UIButton and UILabel text with different font sizes

I have a UIButton and a UILabel displayed inline. They have different size fonts, however I would like to align them so they appear on the same line.
At the moment the UILabel is slight above the baseline of the UIButton.
I was hoping to avoid manually setting a content offset as I want this to scale correctly where possible. I worry manual calculations may have unexpected side effects on changing font sizes etc.
I have created a playground that should show the 2 elements:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
lazy var nameButton = configure(UIButton(type: .system), using: {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.titleLabel?.font = .systemFont(ofSize: 18)
$0.setTitleColor(.darkGray, for: .normal)
$0.contentHorizontalAlignment = .leading
$0.setContentHuggingPriority(UILayoutPriority.defaultHigh, for: .horizontal)
$0.backgroundColor = .lightGray
$0.setTitle("This is a button", for: .normal)
})
lazy var publishedDateLabel = configure(UILabel(frame: .zero), using: {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.font = .systemFont(ofSize: 14)
$0.textColor = .darkGray
$0.setContentHuggingPriority(UILayoutPriority.defaultLow, for: .horizontal)
$0.backgroundColor = .lightGray
$0.text = "and this is a label"
})
override func loadView() {
let view = UIView()
view.backgroundColor = .white
[nameButton, publishedDateLabel].forEach(view.addSubview(_:))
NSLayoutConstraint.activate([
nameButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
nameButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8),
publishedDateLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
publishedDateLabel.leadingAnchor.constraint(equalTo: nameButton.trailingAnchor),
publishedDateLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8)
])
self.view = view
}
// setup helper method
func configure<T>(_ value: T, using closure: (inout T) throws -> Void) rethrows -> T {
var value = value
try closure(&value)
return value
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
I have tried making the label and button the same height by adding publishedDateLabel.heightAnchor.constraint(equalTo: nameButton.heightAnchor)
This didn't change the alignment however.
I also tried using publishedDateLabel.lastBaselineAnchor.constraint(equalTo: nameButton.lastBaselineAnchor)
to align the anchors however this aligned the top of the elements
How can align the bottom of the text in the button to the bottom of the text in the label?
Just comment out the heightAnchor use the lastBaselineAnchor:
NSLayoutConstraint.activate([
nameButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
nameButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8),
publishedDateLabel.lastBaselineAnchor.constraint(equalTo: nameButton.lastBaselineAnchor),
publishedDateLabel.leadingAnchor.constraint(equalTo: nameButton.trailingAnchor),
publishedDateLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8)
])

How to execute activate programmatic constraints

I have written a function that defines and adds a UILabel to the UIView - however the constraints are not being executed as the label remains in the top left corner of the screen.
Heres the code:
func timeAdded(screenHeight: CGFloat, screenWidth: CGFloat, viewController: UIView) {
let readyLabel = UILabel()
readyLabel.backgroundColor = UIColor.white
readyLabel.textAlignment = .center
readyLabel.text = "00:00"
readyLabel.textColor = UIColor.blue
//readyLabel.alpha = 0
readyLabel.font = UIFont(name: "panl-font-4", size: 60)
readyLabel.sizeToFit()
viewController.addSubview(readyLabel)
readyLabel.centerXAnchor.constraint(equalTo: viewController.centerXAnchor, constant: 0).isActive = true
readyLabel.centerYAnchor.constraint(equalTo: viewController.centerYAnchor, constant: 0).isActive = true
}
Any advice appreciated
You may need
readyLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
readyLabel.centerXAnchor.constraint(equalTo: viewController.centerXAnchor)
readyLabel.centerYAnchor.constraint(equalTo: viewController.centerYAnchor)
])
So it's better to read This

In Swift, programmatically creating UIView and adding controls to it and using auto layout, causes the controls to appear on the view's parent

I am trying to write a simple composite component for iOS in Swift 3. It consists of a UILabel followed by an UITextField laid out horizontally followed by a line under them. But What happens is the UILabel disappears, UITextField appears on the parent view and line also disappears.
My design in sketch
What it actually looks like in the Storyboard
My component's constraints in the view controller
My intention was to use Auto Layout, anchor the label to top and leading anchors of the view, anchor the textfield to top of the view and trailing anchor of the label with a constant, so they would appear side by side.
I did do a lot of research on this, one site that looked pretty close to what I wanted was https://www.raywenderlich.com/125718/coding-auto-layout, and I think I am following more or less the same approach.
I am doing something obviously wrong, but can't figure out what. Any help is much appreciated, I have been at this for a few days now.
import UIKit
#IBDesignable
class OTextEdit: UIView {
#IBInspectable var LabelText: String = "Label"
#IBInspectable var SecureText: Bool = false
#IBInspectable var Color: UIColor = UIColor.black
#IBInspectable var Text: String = "" {
didSet {
edit.text = Text
}
}
fileprivate let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 35))
fileprivate let edit = UITextField(frame: CGRect(x: 210, y: 0, width: 200, height: 35))
fileprivate let line: UIView = UIView()
override var intrinsicContentSize: CGSize {
return CGSize(width: 300, height: 100)
}
func setup() {
label.text = LabelText
label.textColor = Color
label.font = UIFont(name: "Avenir Next Condensed", size: 24)
edit.font = UIFont(name: "Avenir Next Condensed", size: 24)
edit.borderStyle = .roundedRect
edit.isSecureTextEntry = SecureText
line.backgroundColor = UIColor.white
self.addSubview(label)
self.addSubview(edit)
self.addSubview(line)
}
override func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)
setup()
setupConstaints()
}
func setupConstaints() {
label.translatesAutoresizingMaskIntoConstraints = false
edit.translatesAutoresizingMaskIntoConstraints = false
line.translatesAutoresizingMaskIntoConstraints = false
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: -10).isActive = true
label.topAnchor.constraint(equalTo: topAnchor)
edit.leadingAnchor.constraint(equalTo: label.leadingAnchor, constant: 10).isActive = true
edit.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
edit.topAnchor.constraint(equalTo: self.topAnchor)
line.heightAnchor.constraint(equalToConstant: 2.0).isActive = true
line.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
line.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
line.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 1.0).isActive = true
}
}
You haven't got a series of constraints top to bottom, so auto layout can't determine the content size of your object. You have tried to set this via the initrinsicContentSize but you shouldn't need to do this.
You also need to set a horizontal hugging priority for your label to let auto layout know that you want the text field to expand:
I removed your override of intrinsicContentSize and changed your constraints to:
Constrain the bottom of the label to the top of the line
Constrain the bottom of the line to the bottom of the superview
Constrain the baseline of the label to the baseline of the text field
Remove the constraint between the top of the text field and the superview
Set the horizontal hugging priority of the label.
func setupConstraints() {
label.translatesAutoresizingMaskIntoConstraints = false
edit.translatesAutoresizingMaskIntoConstraints = false
line.translatesAutoresizingMaskIntoConstraints = false
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
label.setContentHuggingPriority(.defaultHigh, for: .horizontal)
label.topAnchor.constraint(equalTo: topAnchor)
label.bottomAnchor.constraint(equalTo: line.topAnchor, constant: -8).isActive = true
edit.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 10).isActive = true
edit.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
edit.firstBaselineAnchor.constraint(equalTo: label.firstBaselineAnchor).isActive = true
line.heightAnchor.constraint(equalToConstant: 2.0).isActive = true
line.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
line.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
line.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 1.0).isActive = true
line.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
I think it is pretty close to what you are after.

How to size a UIScrollView to fit an unknown amount of text in a UILabel?

I have added a scrollview subview in one of my views, but am having trouble getting it's height to accurately fit the content that the scrollview is showing, which is text in the UILabel. The height needs to be dynamic (i.e. a factor of the text length), because I am instantiating this view for many different text lengths. Whenever I log label.frame.bounds I get (0,0) back. I have also tried sizeToFits() in a few places without much luck.
My goal is to get the scrollview to end when it reaches the last line of text. Also, I am using only programmatic constraints.
A condensed version of my code is the following:
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
let scrollView = UIScrollView()
let containerView = UIView()
let label = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
// This needs to change
scrollView.contentSize = CGSize(width: 375, height: 1000)
scrollView.addSubview(containerView)
view.addSubview(scrollView)
label.text = unknownAmountOfText()
label.backgroundColor = .gray
containerView.isUserInteractionEnabled = true
containerView.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.frame = view.bounds
containerView.frame = CGRect(x: 0, y: 0, width: scrollView.contentSize.width, height: scrollView.contentSize.height)
}
}
Any help is appreciated.
SOLUTION found:
func heightForLabel(text: String, font: UIFont, lineHeight: CGFloat, width: CGFloat) -> CGFloat {
let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.setLineHeight(lineHeight: lineHeight)
label.sizeToFit()
return label.frame.height
}
I found this solution online, that gives me what I need to set the appropriate content size for the scrollView height based on the label's height. Ideally, I'd be able to determine this without this function, but for now I'm satisfied.
The key to UIScrollView and its content size is setting your constraints so that the actual content defines the contentSize.
For a simple example: say you have a UIScrollView with width: 200 and height: 200. Now you put a UIView inside it, that has width: 100 and height: 400. The view should scroll up and down, but not left-right. You can constrain the view to 100x400, and then "pin" the top, bottom, left and right to the sides of the scroll view, and AutoLayout will "auto-magically" set the scrollview's contentSize.
When you add subviews that can change size - either explicitly (code, user interaction) or implicitly - if the constraints are set correctly those changes will also "auto-magically" adjust the scrollview's contentSize.
So... here is an example of what you are trying to do:
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
let scrollView = UIScrollView()
let label = UILabel()
let s1 = "1. This is the first line of text in the label. It has words and punctuation, but no embedded line-breaks, so what you see here is normal UILabel word-wrapping."
var counter = 1
override func viewDidLoad() {
super.viewDidLoad()
// turn off translatesAutoresizingMaskIntoConstraints, because we're going to set them
scrollView.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
// set background colors, just so we can see the bounding boxes
self.view.backgroundColor = UIColor(red: 1.0, green: 0.7, blue: 0.3, alpha: 1.0)
scrollView.backgroundColor = UIColor(red: 0.8, green: 0.8, blue: 1.0, alpha: 1.0)
label.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
// add the label to the scrollView, and the scrollView to the "main" view
scrollView.addSubview(label)
self.view.addSubview(scrollView)
// set top, left, right constraints on scrollView to
// "main" view + 8.0 padding on each side
scrollView.topAnchor.constraint(equalTo: self.topLayoutGuide.bottomAnchor, constant: 8.0).isActive = true
scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 8.0).isActive = true
scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -8.0).isActive = true
// set the height constraint on the scrollView to 0.5 * the main view height
scrollView.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.5).isActive = true
// set top, left, right AND bottom constraints on label to
// scrollView + 8.0 padding on each side
label.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 8.0).isActive = true
label.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 8.0).isActive = true
label.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -8.0).isActive = true
label.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -8.0).isActive = true
// set the width of the label to the width of the scrollView (-16 for 8.0 padding on each side)
label.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -16.0).isActive = true
// configure label: Zero lines + Word Wrapping
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = UIFont.systemFont(ofSize: 17.0)
// set the text of the label
label.text = s1
// ok, we're done... but let's add a button to change the label text, so we
// can "see the magic" happening
let b = UIButton(type: UIButtonType.system)
b.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(b)
b.setTitle("Add a Line", for: .normal)
b.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 24.0).isActive = true
b.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
b.addTarget(self, action: #selector(self.btnTap(_:)), for: .touchUpInside)
}
func btnTap(_ sender: Any) {
if let t = label.text {
counter += 1
label.text = t + "\n\n\(counter). Another line"
}
}
}
give top,left,right and bottom constraint to label with containerView.
and
set label.numberOfLines = 0
also ensure that you have given top, left, right and bottom constraint to containerView. this will solve your issue
Set the auto layout constraints from the interface builder as shown in image .
enter image description here
I set the height of UIScrollView as 0.2 of the UIView
Then drag the UIlabel from MainStoryBoard to the view controller.
Add this two lines in viewdidload method.
draggedlabel.numberOfLines = 0
draggedlabel.lineBreakMode = NSLineBreakMode.byWordWrapping

Resources