Achieving padding from SwiftUI to UIViews - ios

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()

Related

How to put UIView inside UIScrollView in Swift?

I am trying to add a UIView into a UIScrollView without storyboard. I made some code with the given two files(CalcTypeView.siwft and CalcTypeViewController.swift) as below. However, as shown in the screenshot image, I can see the UIScrollView(gray color) while UIView(red color) does not appear on the screen. What should I do more with these code to make UIView appear? (I've already found many example code using single UIViewController, but what I want is UIView + UIViewController form to maintain MVC pattern)
1. CalcTypeView.swift
import UIKit
final class CalcTypeView: UIView {
private let scrollView: UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .gray
view.showsVerticalScrollIndicator = true
return view
}()
private let contentView1: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .red
view.clipsToBounds = true
view.layer.cornerRadius = 10
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupScrollView()
setupContentView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupScrollView() {
self.addSubview(scrollView)
NSLayoutConstraint.activate([
scrollView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
scrollView.widthAnchor.constraint(equalTo: self.widthAnchor),
scrollView.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor),
])
}
private func setupContentView() {
scrollView.addSubview(contentView1)
NSLayoutConstraint.activate([
contentView1.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor, constant: 20),
contentView1.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor, constant: -20),
contentView1.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor, constant: 20),
contentView1.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: -20),
])
}
}
2. CalcTypeViewController.swift
import UIKit
final class CalcTypeViewController: UIViewController {
private let calcTypeView = CalcTypeView()
override func viewDidLoad() {
super.viewDidLoad()
setupNavBar()
setupView()
}
private func setupNavBar() {
let navigationBarAppearance = UINavigationBarAppearance()
navigationBarAppearance.configureWithOpaqueBackground()
navigationBarAppearance.shadowColor = .clear
navigationController?.navigationBar.standardAppearance = navigationBarAppearance
navigationController?.navigationBar.scrollEdgeAppearance = navigationBarAppearance
navigationController?.navigationBar.tintColor = Constant.ColorSetting.themeColor
navigationController?.navigationBar.prefersLargeTitles = false
navigationController?.setNeedsStatusBarAppearanceUpdate()
navigationController?.navigationBar.isTranslucent = false
navigationItem.scrollEdgeAppearance = navigationBarAppearance
navigationItem.standardAppearance = navigationBarAppearance
navigationItem.compactAppearance = navigationBarAppearance
navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "bookmark.fill"), style: .plain, target: self, action: #selector(addButtonTapped))
navigationItem.rightBarButtonItem?.tintColor = Constant.ColorSetting.themeColor
navigationItem.title = Constant.MenuSetting.menuName2
self.extendedLayoutIncludesOpaqueBars = true
}
override func loadView() {
view = calcTypeView
}
private func setupView() {
view.backgroundColor = .systemBackground
}
#objc private func addButtonTapped() {
let bookmarkVC = BookmarkViewController()
navigationController?.pushViewController(bookmarkVC, animated: true)
}
}
My Screenshot
A scroll view's contentLayoutGuide defines the size of the scrollable area of the scroll view. The default size is 0,0.
In your code your contentView1 has no intrinsic size. It simply has a default size of 0,0. So your constraints are telling the scroll view to make its contentLayoutGuide to be 40,40 (based on the 20 and -20 constants) and leave the contentView1 size as 0,0.
If you setup contentView1 with specific width and height constraints then the scroll view's content size would be correct so that contentView1 would scroll within the scroll view.
A better example might be to add a UIStackView with a bunch of labels. Since the stack view will have an intrinsic size based on its content and setup, the contentLayoutGuide of the scroll view will fit around the stack view's intrinsic size.

How do you ignore the safe area in UIKit?

I know you use ignoresSafeArea() in SwiftUI to make some UI-element-frame dismiss the safe area. However, when using UIKit I don't know how to do it. I want to place a UILabel right below the actual screen, but I cannot see it because it is hidden behind the safe area.
import UIKit
class SignInViewController: UIViewController {
private let headline = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
addLabel()
view.backgroundColor = .black
}
func addLabel() {
headline.text = "SmashPass"
headline.textColor = UIColor.red
headline.frame = CGRect(x: 0 , y: 0 , width: 243, height: 29)
headline.textAlignment = .center
// alignment
headline.center.x = view.center.x
headline.center.y = view.frame.minY - 10
view.addSubview(headline)
}
}
Learn about how to use Auto-Layout...
Here is your code, with modifications to use Constraints and Auto-Layout to place the label at the bottom of the view:
class SignInViewController: UIViewController {
private let headline = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
addLabel()
view.backgroundColor = .black
}
func addLabel() {
headline.text = "SmashPass"
headline.textColor = UIColor.red
headline.textAlignment = .center
// -- use Auto-Layout!
// alignment
//headline.frame = CGRect(x: 0 , y: 0 , width: 243, height: 29)
//headline.center.x = view.center.x
//headline.center.y = view.frame.minY - 10
view.addSubview(headline)
headline.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// label uses full-width
headline.leadingAnchor.constraint(equalTo: view.leadingAnchor),
headline.trailingAnchor.constraint(equalTo: view.trailingAnchor),
// constrain the label bottom to the view bottom
headline.bottomAnchor.constraint(equalTo: view.bottomAnchor),
// label height is 29-points
headline.heightAnchor.constraint(equalToConstant: 29.0),
])
}
}
Note: in general, we want to constrain elements with awareness of the Safe-Area...
Using that code, we'll get this on an iPhone 12 Pro (for example):
and, if we set headline.backgroundColor = .yellow so we can see the label's frame:

UIView width and height adjustment according to it subview programatically

I want to create something like this:
There's a white box under the buttons. If we are using SwiftUI logic, it's vertical padding : 5 and horizontal padding : 10, to create it with SwiftUI is pretty easy, but from what I have learned there is no padding and background color to a UIStackView and to create something like this, you need a UIView then add the stack view on top of the UIView.
This is what I have done so far:
//
// TransaksiViewController.swift
// HaselWiratama
//
// Created by Farhandika on 18/09/21.
// Copyright © 2021 Hasel.id. All rights reserved.
//
import UIKit
class TransaksiViewController: UIViewController {
let pesanButton: BigButton = {
let button = BigButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .blue
button.configure(viewModel: MyCustomBigButton(title: "Phone",
imageName: "house", isSystemImage: false))
return button
}()
let ambulanceButton: BigButton = {
let button = BigButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .blue
button.configure(viewModel: MyCustomBigButton(title: "Phone",
imageName: "house", isSystemImage: false))
return button
}()
let topStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.distribution = .fillEqually
stackView.spacing = 10
return stackView
}()
let uiView = UIView()
func configureUIView() {
//Configure the stackview
topStackView.addArrangedSubview(pesanButton)
topStackView.addArrangedSubview(ambulanceButton)
// add stack to UIView
uiView.addSubview(topStackView)
NSLayoutConstraint.activate([
topStackView.heightAnchor.constraint(equalToConstant: 150),
topStackView.centerYAnchor.constraint(equalTo: uiView.centerYAnchor),
topStackView.centerXAnchor.constraint(equalTo: uiView.centerXAnchor)
])
uiView.translatesAutoresizingMaskIntoConstraints = false
uiView.backgroundColor = .purple
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .cyan
view.addSubview(uiView)
configureUIView()
NSLayoutConstraint.activate([
uiView.widthAnchor.constraint(equalToConstant: 500),
uiView.heightAnchor.constraint(equalToConstant: 400),
uiView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
uiView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
}
/* Ignore the button width and height because I have not add the constraint yet */
The result:
As you can see, the width and height of the UIView is not relative to its child view.
How do I emulate the same padding horizontal 10 and vertical 5 in UIView?
(Basically a progressive or responsive width and height of a UIView.)
Since you have mentioned that you used constraints, see the following code. It reflect a UIViewController with what you need:
class ViewController: UIViewController {
// Horizontal Stackview
lazy var stack: UIStackView = {
let view = UIStackView()
view.translatesAutoresizingMaskIntoConstraints = false
view.axis = .horizontal
view.spacing = 10 // Inter-item space
view.backgroundColor = .white
view.distribution = .fillEqually // Setting distribution to fill equally
return view
}()
// Button 1
lazy var button1: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Button 1", for: .normal)
button.backgroundColor = .red
return button
}()
// Button 2
lazy var button2: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Button 2", for: .normal)
button.backgroundColor = .blue
return button
}()
// View that holds the stackview
lazy var stackHolder: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .gray
view.addSubview(stackHolder)
stackHolder.addSubview(stack)
stack.addArrangedSubview(button1)
stack.addArrangedSubview(button2)
//Setting layout constraints
NSLayoutConstraint.activate([
stackHolder.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackHolder.centerYAnchor.constraint(equalTo: view.centerYAnchor),
// Setting a width and height of the stack so that the `stackHolder` adjust relatively
stack.widthAnchor.constraint(equalToConstant: 250),
stack.heightAnchor.constraint(equalToConstant: 80),
// Setting the constraints with `constant` values for padding
stack.leadingAnchor.constraint(equalTo: stackHolder.leadingAnchor, constant: 5),
stack.trailingAnchor.constraint(equalTo: stackHolder.trailingAnchor, constant: -5),
stack.topAnchor.constraint(equalTo: stackHolder.topAnchor, constant: 10),
stack.bottomAnchor.constraint(equalTo: stackHolder.bottomAnchor, constant: -10),
])
}
}
This will output:

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) {
}
}

Swift - Dynamically resize view after label in it resizes dynamically

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."
}
}

Resources