I'd like to create a simple app without Storyboard. I've created a closure outside the viewDidLoad method, which represents a title on the screen. My problem is that the code contains duplicated lines view.addSubview(label) and it positions the label to the wrong place.
Could you please help me solving this issue?
class HomeVC: UIViewController {
let titleLabel: UILabel = {
let view = UIView()
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
label.text = "Hello"
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(titleLabel)
}
}
I think you want to set the label at the center of HomeVC's view, the problem in the above code is that you are making a new view and place the label inside the view and thats not what you want , so
You just make label first like this:
let titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Hello"
return label
}()
and then in viewDidLoad add this label as subview of view and apply constraints
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(titleLabel)
setupTitleLabel()
}
func setupTitleLabel() {
titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
titleLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
//you also need to give the label height and width constraints to label here...
}
Related
I have one textField.
private var verseTitle: UITextField = {
let tf = UITextField()
tf.placeholder = "TITLE"
tf.font = UIFont(suite16: .tBlackItalic, size: 18)
tf.textColor = .black.withAlphaComponent(0.5)
tf.returnKeyType = .done
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
In viewDidLoad method, I have assigned self as delegate.
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
verseTitle.delegate = self
}
In viewDidLayout method, I'm using stack view to add textField to the view.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
//Title
let titleStack = UIStackView()
titleStack.axis = .horizontal
titleStack.alignment = .center
titleStack.distribution = .equalSpacing
titleStack.spacing = 8
titleStack.addArrangedSubview(verseTitle)
titleStack.addArrangedSubview(floorView)
titleStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(titleStack)
NSLayoutConstraint.activate([
floorView.heightAnchor.constraint(equalToConstant: 13),
floorView.widthAnchor.constraint(equalToConstant: 13),
titleStack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
titleStack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
])
}
Now, the problem I'm facing is, that when I start typing in the textField, the keyboard gets dismissed only after I type one letter. I'm not sure why this is happening. I have to tap on the field after entering each letter. For some reason, the focus is taken away from the field after each letter is entered (unless I tap on a suggested autocorrect - the whole string is correctly added to the string at once)
What's going on here is your view, including the UITextField gets re-created after each keystroke because your view construction is in: viewDidLayoutSubviews(), When the bounds change for a view controller's view, the view adjusts the positions of its subviews and then the system calls this method.
To fix it, move the code to ViewDidLoad, so the view is created only once:
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
verseTitle.delegate = self
let titleStack = UIStackView()
titleStack.axis = .horizontal
titleStack.alignment = .center
titleStack.distribution = .equalSpacing
titleStack.spacing = 8
titleStack.addArrangedSubview(verseTitle)
titleStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(titleStack)
NSLayoutConstraint.activate([
titleStack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
titleStack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
])
}
UISegmentedControl corner radius is not changing. I also followed some answers in this question, my UISegmentedControl's corner radius still is not changing. I followed This tutorial to create UISegmentedControl.
Code:
import UIKit
class SegmentViewController: UIViewController {
private let items = ["Black", "Red", "Green"]
lazy var segmentedConrol: UISegmentedControl = {
let control = UISegmentedControl(items: items)
return control
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setupViews()
}
fileprivate func setupViews(){
view.addSubview(segmentedConrol)
segmentedConrol.translatesAutoresizingMaskIntoConstraints = false //set this for Auto Layout to work!
segmentedConrol.heightAnchor.constraint(equalToConstant: 40).isActive = true
segmentedConrol.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 40).isActive = true
segmentedConrol.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -40).isActive = true
segmentedConrol.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
segmentedConrol.selectedSegmentIndex = 1
//style
segmentedConrol.layer.cornerRadius = 20
segmentedConrol.layer.borderWidth = 2
segmentedConrol.layer.borderColor = UIColor.black.cgColor
segmentedConrol.backgroundColor = .red
segmentedConrol.selectedSegmentTintColor = .darkGray
// segmentedConrol.clipsToBounds = true
segmentedConrol.layer.masksToBounds = true
}
}
(PS. Probably the answer is so simple for most people, please do not mind me, I am new in this field.)
Subclass UISegmentedControl and override layoutSubviews. Inside the method set the corner radius to what you want it to be, and you can remove the portion where you set the corner radius in setupViews():
class YourSegmentedControl: UISegmentedControl {
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = 20
}
}
In your view controller where you create segmentedControl create an instance of YourSegmentedControl like below.
lazy var segmentedConrol: YourSegmentedControl = {
let control = YourSegmentedControl(items: items)
return control
}()
The result is:
I have created a custom view which has an image and a label. I added a tap gesture on it, so when user taps on the view, both label and custom view expands to show the details/more text on the label.
I added code to expand the label which works fine, but the UIView/custom view doesn't get expanded. Below is my code to expand the label.
How do I expand the custom view either programmatically or by adding any constraints?
#objc func bannerTapped(_ sender:UITapGestureRecognizer){
self.bannerMessageLabel.numberOfLines = 0
self.bannerMessageLabel.text = "Unicode characters take up multiple GSM characters. When a Unicode symbol appears in a text, it is usually segmented at the 70-character mark, thus making it even harder for the recipient to decipher the message."
self.bannerMessageLabel.sizeToFit()
self.bannerView.setNeedsLayout()
self.bannerView.layoutIfNeeded()
}
Attaching image. I don't have any constraints on them since I am leaving it on Stack view to handle it.
I created a sample ViewController that does what you need. I think what you were missing is the following anchor:
bannerView.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor).isActive = true
Sample View Controller:
class TestViewController: UIViewController {
// MARK: - UIObjects
private let bannerView: UIView = {
let bannerView = UIView()
bannerView.translatesAutoresizingMaskIntoConstraints = false
bannerView.backgroundColor = .white
return bannerView
}()
private let stackView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical // Works for both horizontal and vertical axis
return stackView
}()
private let bannerWarningImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "arrow-right")
imageView.backgroundColor = .orange
return imageView
}()
private let bannerMessageLabel: UILabel = {
let label = UILabel()
label.text = "Unicode characters take up multiple GSM characters. When a Unicode symbol appears in a text"
label.numberOfLines = 0
label.backgroundColor = .green
return label
}()
// MARK: - Overrides
override func viewDidLoad() {
super.viewDidLoad()
bannerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(bannerTapped)))
addUIElements()
addConstrains()
}
// MARK: - UISetup
private func addUIElements() {
view.backgroundColor = .purple
view.addSubview(bannerView)
bannerView.addSubview(stackView)
stackView.addArrangedSubview(bannerWarningImage)
stackView.addArrangedSubview(bannerMessageLabel)
}
private func addConstrains() {
bannerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
bannerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
bannerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
bannerView.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: bannerView.topAnchor).isActive = true
// The -10 for you to see that the bannerView height is actually chaning
stackView.bottomAnchor.constraint(equalTo: bannerView.bottomAnchor, constant: -10).isActive = true
stackView.leadingAnchor.constraint(equalTo: bannerView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: bannerView.trailingAnchor).isActive = true
}
// MARK: - Actions
#objc private func bannerTapped() {
self.bannerMessageLabel.text = "Unicode characters take up multiple GSM characters. When a Unicode symbol appears in a text, it is usually segmented at the 70-character mark, thus making it even harder for the recipient to decipher the message."
}
}
In SwiftUI, you could easily achieving padding via the following code:
Text("Hello World!")
.padding(20)
What options do I have to achieve the same in UIKit?
You can use storyboard for same and easily make this layout by combining UIView and Label.
Just add once view in view controller and centre align vertically and horizontally. Then add label inside that view and set top, bottom, leading, trailing to 20 respective to that view.
Just check following image.
This will make exactly same output as you want.
Using a UIStackView, something like this:
class PaddingableView: UIView {
var padding = UIEdgeInsets.zero { didSet { contentStackView.layoutMargins = padding } }
// MARK: Subviews
private lazy var contentStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .fill
stackView.distribution = .fill
stackView.isLayoutMarginsRelativeArrangement = true
stackView.layoutMargins = padding
return stackView
}()
// MARK: Functions
private func addContentStackView() {
super.addSubview(contentStackView)
NSLayoutConstraint.activate([
contentStackView.topAnchor.constraint(equalTo: topAnchor),
contentStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
contentStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
contentStackView.trailingAnchor.constraint(equalTo: trailingAnchor)
])
}
override func addSubview(_ view: UIView) {
contentStackView.addArrangedSubview(view)
}
override func layoutSubviews() {
if contentStackView.superview == nil { addContentStackView() }
super.layoutSubviews()
}
}
Note that using too many nested stackviews can hit the performance
Ok, most simple is of corse Storyboard-based approach (as shown by #Sagar_Chauhan)...
Below is provided line-by-line Playground variant (as to compare with SwiftUI Preview by lines of code, for instance).
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 375, height: 677))
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
self.view = view
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let label = UILabel(frame: .zero)
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Hello World!"
label.textColor = .black
label.backgroundColor = .yellow
label.textAlignment = .center
self.view.addSubview(label)
let fitSize = label.sizeThatFits(view.bounds.size)
label.widthAnchor.constraint(equalToConstant: fitSize.width + 12.0).isActive = true
label.heightAnchor.constraint(equalToConstant: fitSize.height + 12.0).isActive = true
label.topAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
label.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
}
}
PlaygroundPage.current.liveView = MyViewController()
I need to extend my navigation bar height but since Apple made it very hard to change the navigation bar height in iOS 11 I decided I needed to use a custom view which extended the navigation bar without the user noticing.
I've created a custom view to add to the bottom of the navigation bar. I made it red just for the sake of making this question more clear. When the user leaves the view controller and then comes back, the title view custom view is "clipped" by the red view. Why?
I've tried to set clipsToBounds false on the custom title view, but that didn't help. How can I make sure the custom title view always stays on top of everything? Why is it being clipped and overlapped by the little red view (whose main purpose is to "extend" the navigation bar)?
Note: "Monthly Spending" label is part of the title view being clipped.
class ViewController: UIViewController {
let customTitleView = CustomTitleView()
let navigationBarExtensionView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
setupAdditionalGradientView()
navigationItem.titleView = customTitleView
}
internal func setupAdditionalGradientView() {
view.addSubview(navigationBarExtensionView)
navigationBarExtensionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
navigationBarExtensionView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
navigationBarExtensionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
navigationBarExtensionView.heightAnchor.constraint(equalToConstant: 18).isActive = true
// Hide pixel shadow between nav bar and red bar
navigationController?.navigationBar.shadowImage = UIImage()
navigationController?.navigationBar.layer.shadowRadius = 0
navigationController?.navigationBar.layer.shadowOffset = CGSize(width: 0, height: 0)
}
}
Custom title view:
import UIKit
class CustomTitleView: UIView {
let primaryLabel: UILabel = {
let label = UILabel()
label.text = "$10,675.00"
label.font = UIFont.systemFont(ofSize: 27.99, weight: .medium)
label.textColor = .white
label.textAlignment = .center
return label
}()
let secondaryLabel: UILabel = {
let label = UILabel()
label.text = "Monthly Spending"
label.textColor = .white
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 10, weight: .medium)
return label
}()
let stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .fillProportionally
stackView.alignment = .center
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupStackView()
}
internal func setupStackView() {
addSubview(stackView)
stackView.addArrangedSubview(primaryLabel)
stackView.addArrangedSubview(secondaryLabel)
stackView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 10).isActive = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
In iOS 11, a custom bar button item view such as your titleView is sized from the inside out using constraints. Thus, you need constraints to size the view correctly. You are not providing any constraints, so the runtime doesn't know how to size the title view.
However, I would suggest that you just give up on the dubious idea of extending your UINavigationItem's custom view downward below the outside of the navigation bar, and instead, just show the words Monthly Spending in your view controller's view.