safeAreaLayoutGuide guide not working from another viewcontroller - ios

As suggested by some experts like #DonMag , i design the view in a separate controller, while trying to do so , every thing works, but when i use a navigation controller and there is a navigation bar at top, if i try and design in separate controller and then add its view to another controller the safeAreaLayoutGuide does not work and the view is attached to top of screen ignoring the safearea
SOLUTION as per #Mohammad Azam, DonMag solutions works as well , thanks
import UIKit
class NotesDesign: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func commonInit(){
let notesTitle = UITextField()
let notesContent = UITextView()
let font = UIFont(name: "CourierNewPS-ItalicMT", size: 20)
let fontM = UIFontMetrics(forTextStyle: .body)
notesTitle.font = fontM.scaledFont(for: font!)
notesContent.font = fontM.scaledFont(for: font!)
notesTitle.widthAnchor.constraint(greaterThanOrEqualToConstant:250).isActive = true
notesTitle.heightAnchor.constraint(equalToConstant: 30).isActive = true
notesTitle.borderStyle = .line
notesContent.widthAnchor.constraint(greaterThanOrEqualToConstant:250).isActive = true
notesContent.heightAnchor.constraint(greaterThanOrEqualToConstant: 100).isActive = true
notesContent.layer.borderWidth = 1
let notesStack = UIStackView()
notesStack.axis = .vertical
notesStack.spacing = 20
notesStack.alignment = .top
notesStack.distribution = .fill
notesStack.addArrangedSubview(notesTitle)
notesStack.addArrangedSubview(notesContent)
// Do any additional setup after loading the view.
notesStack.translatesAutoresizingMaskIntoConstraints = false
addSubview(notesStack)
notesStack.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 10).isActive = true
notesStack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
notesStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10).isActive = true
notesStack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -40).isActive = true
}
}
And where i call it
import UIKit
class AddNotesViewController: UIViewController {
var design = NotesDesign()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
view.addSubview(design)
design.translatesAutoresizingMaskIntoConstraints = false
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .plain, target: self, action: #selector(saveData))
}
#objc func saveData() {
}
}
And this is what i get

If you are taking a view from its ViewController and adding it as a subview to another view, you need to constrain it just like you would with any other added subview:
class AddNotesViewController: UIViewController {
var design = NotesViewDesign()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
view.addSubview(design.view)
// a view loaded from a UIViewController has .translatesAutoresizingMaskIntoConstraints = true
design.view.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain the loaded view
design.view.topAnchor.constraint(equalTo: g.topAnchor),
design.view.leadingAnchor.constraint(equalTo: g.leadingAnchor),
design.view.trailingAnchor.constraint(equalTo: g.trailingAnchor),
design.view.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .plain, target: self, action: #selector(saveData))
}
#objc func saveData() {
}
}

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.

Custom view with textField and button not responding

I have custom view with some labels, a text field and a button. When I add it to a view in a VC, the custom view appears nicely, but I can't tap on the text field or on the button, they are not responding. Can someone tell my what is the problem with the code? Here is the simplified version:
import UIKit
class CustomView: UIView {
let title: UILabel = {
let title = UILabel()
title.font = UIFont.systemFont(ofSize: 24)
title.text = "Title"
title.numberOfLines = 0
title.textAlignment = .center
title.translatesAutoresizingMaskIntoConstraints = false
return title
}()
let textView: UITextField = {
let textView = UITextField()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.placeholder = "Placeholder text"
textView.backgroundColor = UIColor.lightGray
textView.layer.cornerRadius = 10
return textView
}()
let searchButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = UIColor.blue
button.setTitle("Tap me", for: UIControl.State.normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
return button
}()
private func setupView() {
addSubview(title)
addSubview(textView)
addSubview(searchButton)
NSLayoutConstraint.activate([
title.topAnchor.constraint(equalTo: self.topAnchor, constant: 100),
title.rightAnchor.constraint(equalTo: self.rightAnchor),
title.leftAnchor.constraint(equalTo: self.leftAnchor),
])
NSLayoutConstraint.activate([
textView.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 20),
textView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
textView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
textView.heightAnchor.constraint(equalToConstant: 60)
])
NSLayoutConstraint.activate([
searchButton.topAnchor.constraint(equalTo: textView.bottomAnchor, constant: 20),
searchButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
searchButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
searchButton.heightAnchor.constraint(equalToConstant: 60)
])
}
#objc func buttonAction(_ sender:UIButton!)
{
print("Button tapped")
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setupView()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
import UIKit
class ViewController: UIViewController {
private let customView = CustomView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(customView)
customView.translatesAutoresizingMaskIntoConstraints = false
customView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
customView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
}
Basically I want to have a seperate file with the view that has all the design and I just want to drop that into the VC without doing anything special with it. Is this even a good approach? Where should I set up the button action? I mean once its tappable...
Thanks!
Try this first - at the end of viewDidLoad(), add this line:
customView.backgroundColor = .red
When you run the app, you'll notice there is no red box.
Now, add this line after that one:
customView.clipsToBounds = true
Run it again, and... we see nothing!
The problem is, you haven't given your customView any height.
To fix it, constrain the bottom of searchButton in your custom view class:
NSLayoutConstraint.activate([
searchButton.topAnchor.constraint(equalTo: textView.bottomAnchor, constant: 20),
searchButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
searchButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
searchButton.heightAnchor.constraint(equalToConstant: 60),
// add this line!
searchButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20),
])
You need to set userInteractionEnabled to true on your CustomView instance so that it passes through taps.
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(customView)
customView.translatesAutoresizingMaskIntoConstraints = false
customView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
customView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
customView.userInteractionEnabled = true
}
You can use a delegation pattern or a closure property to pass the button tap event back to the containing view controller.
For example,
class CustomView: UIView {
var searchTappedHandler: ((CustomView)->Void)?
#objc func buttonAction(_ sender:UIButton!)
{
print("Button tapped")
self.searchTappedHandler?(self)
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(customView)
customView.translatesAutoresizingMaskIntoConstraints = false
customView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
customView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
customView.userInteractionEnabled = true
customView.searchTappedHandler = { _ in
print("Search button was tapped")
}
}
Or, if you want to use a function rather than an inline closure
func handleTap(_ customView: CustomView) {
print("tap")
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(customView)
customView.translatesAutoresizingMaskIntoConstraints = false
customView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
customView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
customView.userInteractionEnabled = true
customView.searchTappedHandler = handleTap
}

UIViewController with custom UIView (that includes a button) doesn't recognize taps

When trying to load a custom UIView (BaseHeader.swift) that contains a UIButton into a UIViewController (ViewController.swift) programmatically, the taps on the button are not recognized.
If I extract the code within the custom UIView and paste it directly into the UIViewController, all taps are recognized and working as expected. What am I missing?
At first, I thought it was a problem with not defining a frame size for the UIView after it gets instantiated. I thought that there might be no "hit box" for the button despite it being displayed as intended. After giving the view a frame I still had no luck and tried various other things after googling about for awhile.
The view loads but button taps are not recognized -- see image
ViewController.swift
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = "Authentication"
}
override func loadView() {
super.loadView()
let header = BaseHeader()
header.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(header)
NSLayoutConstraint.activate([
header.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
header.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
header.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: -10),
])
}
}
BaseHeader.swift
import UIKit
class BaseHeader: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func setupView() {
self.isUserInteractionEnabled = true
let avenirFont = UIFont(name: "Avenir", size: 40)
let lightAvenirTitle = avenirFont?.light
let title = UILabel()
title.text = "Title"
title.font = lightAvenirTitle
title.textColor = .black
title.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(title)
NSLayoutConstraint.activate([
title.topAnchor.constraint(equalTo: self.topAnchor, constant: 20),
title.centerXAnchor.constraint(equalTo: self.centerXAnchor)
])
let profile = UIButton()
let profileImage = UIImage(named: "person-icon")
profile.setImage(profileImage, for: .normal)
profile.setImage(UIImage(named: "think-icon"), for: .highlighted)
profile.addTarget(self, action: #selector(profileTapped), for: .touchUpInside)
profile.backgroundColor = .systemBlue
profile.frame.size = CGSize(width: 30, height: 30)
profile.isUserInteractionEnabled = true
profile.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(profile)
NSLayoutConstraint.activate([
profile.centerYAnchor.constraint(equalTo: title.centerYAnchor),
profile.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20),
])
}
#objc func profileTapped(sender: UIButton) {
print("Tapped")
}
}
You forgot set height for BaseHeader
NSLayoutConstraint.activate([
header.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
header.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
header.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: -10),
header.heightAnchor.constraint(equalToConstant: 40) //add more
])
It might be that you are calling the wrong initializer for your custom view and that your setupView() function is not being called. It looks like in the view controller you are calling
let header = BaseHeader()
To initialize the view, but it's this init:
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
That calls your setupView() function and sets up the tap.

class added dynamically doesn't respond touched in swift

I declare TestClass contain button and textField and constraint it and then add this class to view controller
import Foundation
import UIKit
class TestClass : UIView {
let testButton : UIButton = {
let button = UIButton()
button.setTitle("Test", for: .normal)
button.setTitleColor(.red ,for:.normal)
return button
}()
let testInput : UITextField = {
let input = UITextField()
input.placeholder = "type here"
return input
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
func setupView(){
addSubview(testButton)
addSubview(testInput)
testButton.translatesAutoresizingMaskIntoConstraints = false
testInput.translatesAutoresizingMaskIntoConstraints = false
testButton.topAnchor.constraint(equalTo: self.topAnchor, constant: 20).isActive = true
testButton.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20).isActive = true
testButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
testButton.widthAnchor.constraint(equalToConstant: 50).isActive = true
testInput.topAnchor.constraint(equalTo: self.topAnchor, constant: 70).isActive = true
testInput.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20).isActive = true
testInput.heightAnchor.constraint(equalToConstant: 30).isActive = true
testInput.widthAnchor.constraint(equalToConstant: 100).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
in the viewDidLoad I add this class and it appeared .
but when I tap on textField or button it doesn't respond
class ViewControll : UIViewController {
let newClass : TestClass = {
let view = TestClass()
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(newClass)
newClass.frame.size = CGSize(width: 200, height: 200)
newClass.translatesAutoresizingMaskIntoConstraints = false
newClass.topAnchor.constraint(equalTo: contView.topAnchor, constant: 300).isActive = true
newClass.leadingAnchor.constraint(equalTo: contView.leadingAnchor, constant: 30).isActive = true
newClass.testButton.addTarget(self, action: #selector(prnt), for: .touchUpInside)
}
#objc func prnt(){
print("bla bla bla")
}
}
when I click on button or tap on textField nothing happened
can anyone help me please
Constrains don't define height and width of your TestClass - set frame doesn't work. You need to add constraints for this as well, for example, in viewDidLoad method:
newClass.heightAnchor.constraint(equalToConstant: 200).isActive = true
newClass.widthAnchor.constraint(equalToConstant: 200).isActive = true

Subview not getting displayed

I am trying to add a subview using layout constraint. The subview is not getting displayed. Its displaying only when the frame is specified. However I want to achieve the it using layout constraints
import UIKit
class PhotoViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .lightGray
configurePhotoBarButton()
buildSubView()
}
#objc func closeClicked() {
dismiss(animated: true, completion: nil)
}
func configurePhotoBarButton() {
navigationController?.navigationBar.barStyle = .black
navigationController?.navigationBar.barTintColor = .lightGray
navigationItem.title = "Take a Photo"
let titleAttributes = [NSAttributedString.Key.font:UIFont(name: "DIN Condensed", size: 30),NSAttributedString.Key.foregroundColor: UIColor.black]
self.navigationController?.navigationBar.titleTextAttributes = titleAttributes
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Close", style: .plain, target: self, action: #selector(closeClicked))
let attributes = [NSAttributedString.Key.font:UIFont(name: "DIN Condensed", size: 20),NSAttributedString.Key.foregroundColor: UIColor.black]
navigationItem.leftBarButtonItem?.setTitleTextAttributes(attributes, for: .normal)
}
func buildSubView() {
//Add a Image View
let testview = UIView()
testview.backgroundColor = .orange
self.view.addSubview(testview)
testview.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
testview.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20).isActive = true
testview.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true
testview.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20).isActive = true
}
}
You are missing set translatesAutoresizingMaskIntoConstraints.
Please set it equal false when you want to add constraint programmatically.
testview.translatesAutoresizingMaskIntoConstraints = false

Resources