Safe Area Layout Guide Not Working On UITableView's BackgroundView - swift

The issue I'm having is that the label is anchored to the bottom edge of the screen when it should be anchored to the safe area layout guide. which will bring the label above the iPhone line.
Here's the code...
class CustomTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView(frame: .zero)
tableView.backgroundView = CustomBackgroundView()
}
}
.
class CustomBackgroundView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupSubviews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupSubviews() {
let label = UILabel()
label.text = "Hello, World!"
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor).isActive = true
}
}

You are seeing this behavior because every UIView has its own SafeAreaLayoutGuide. Per default the SafeAreaLayoutGuide of a generic UIView subclass does not include the Safe Areas that you are looking for. You have to use the SafeAreaLayoutGuide of your table view.
You could do something like this:
class CustomBackgroundView: UIView {
var safetyAreaBottomAnchor: NSLayoutYAxisAnchor? {
didSet {
guard let safetyAreaBottomAnchor = safetyAreaBottomAnchor else { return }
label.bottomAnchor.constraint(equalTo: safetyAreaBottomAnchor).isActive = true
}
}
private let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupSubviews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupSubviews() {
label.text = "Hello, World!"
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
}
}
And then in your UITableViewController do this:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let customBackgroundView = CustomBackgroundView()
tableView.tableFooterView = UIView(frame: .zero)
tableView.backgroundView = customBackgroundView
customBackgroundView.safetyAreaBottomAnchor = tableView.safeAreaLayoutGuide.bottomAnchor
}

For anyone arriving from Google after all these years, to get this to work I had to return the safeAreaLayoutGuide of the superview from my custom view. See below:
class CustomBackgroundView: UIView {
override var safeAreaLayoutGuide: UILayoutGuide {
guard let superview = superview else { return UILayoutGuide() }
return superview.safeAreaLayoutGuide
}
}
This made all the constraints work as expected. Make sure you don't use the safeAreaLayoutGuide before adding the background view
func setupBackgroundViews()
{
tableView.backgroundView = customBackgroundView
anotherView.topAnchor.constraint(equalTo: customBackgroundView.safeAreaLayoutGuide.topAnchor).isActive = true
}

Related

inputAccessoryView sizing problem on iPhones without physical home button

inputAccessoryView's background view is falling under its own textField and profile picture imageView.
It works fine on regular screen iPhones, but on new iPhones with notches it looks like this:
Here's how it looks animated when keyboard appears: Transition animation on becomeFirstResponder()
Here's my tableView in which I'm trying to add accessoryView:
import UIKit
import SDWebImage
class CommentsTableViewController: UITableViewController {
let viewModel = CommentsViewModel()
let postID: String
let postCaption: String
let postDate: Date
let postAuthor: ZoogramUser
var keyboardAccessoryView: CommentAccessoryView = {
let commentAccessoryView = CommentAccessoryView()
return commentAccessoryView
}()
init(post: UserPost) {
self.postID = post.postID
self.postCaption = post.caption
self.postDate = post.postedDate
self.postAuthor = post.author
super.init(style: .grouped)
self.tableView.register(PostCommentsTableViewCell.self, forCellReuseIdentifier: PostCommentsTableViewCell.identifier)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Comments"
keyboardAccessoryView.delegate = self
configureKeyboardAccessoryView()
viewModel.getComments(for: self.postID) {
self.tableView.reloadData()
}
tableView.backgroundColor = .systemBackground
tableView.keyboardDismissMode = .interactive
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 100
tableView.allowsSelection = false
tableView.separatorStyle = .none
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
becomeFirstResponder()
}
override var inputAccessoryView: UIView? {
keyboardAccessoryView.heightAnchor.constraint(greaterThanOrEqualToConstant: 60).isActive = true
keyboardAccessoryView.backgroundColor = .systemOrange
return keyboardAccessoryView
}
override var canBecomeFirstResponder: Bool {
return true
}
func configureKeyboardAccessoryView() {
guard let photoURL = AuthenticationManager.shared.getCurrentUserProfilePhotoURL() else {
return
}
keyboardAccessoryView.userProfilePicture.sd_setImage(with: photoURL)
}
}
And here's code for my CommentAccessoryView which I use to override inputAccessoryView:
import UIKit
protocol CommentAccessoryViewProtocol {
func postButtonTapped(commentText: String)
}
class CommentAccessoryView: UIView {
var delegate: CommentAccessoryViewProtocol?
var userProfilePicture: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
imageView.backgroundColor = .secondarySystemBackground
imageView.contentMode = .scaleAspectFill
return imageView
}()
var commentTextField: AccessoryViewTextField = {
let textField = AccessoryViewTextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.backgroundColor = .systemBackground
textField.placeholder = "Enter comment"
textField.clipsToBounds = true
textField.layer.borderWidth = 1
textField.layer.borderColor = UIColor.placeholderText.cgColor
return textField
}()
var postButton: UIButton = {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.widthAnchor.constraint(equalToConstant: 30).isActive = true
button.heightAnchor.constraint(equalToConstant: 30).isActive = true
button.clipsToBounds = true
button.layer.cornerRadius = 30/2
button.setImage(UIImage(systemName: "arrow.up.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 35)), for: .normal)
button.tintColor = .systemBlue
button.addTarget(self, action: #selector(didTapPostButton), for: .touchUpInside)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupConstraints()
backgroundColor = .systemBackground
commentTextField.rightView = postButton
commentTextField.rightViewMode = .always
autoresizingMask = .flexibleHeight
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
setViewCornerRadius()
}
func setViewCornerRadius() {
userProfilePicture.layer.cornerRadius = userProfilePicture.frame.height / 2
commentTextField.layer.cornerRadius = commentTextField.frame.height / 2
}
func setupConstraints() {
self.addSubviews(userProfilePicture, commentTextField)
NSLayoutConstraint.activate([
userProfilePicture.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10),
userProfilePicture.centerYAnchor.constraint(equalTo: self.safeAreaLayoutGuide.centerYAnchor),
userProfilePicture.widthAnchor.constraint(equalToConstant: 40),
userProfilePicture.heightAnchor.constraint(equalToConstant: 40),
commentTextField.leadingAnchor.constraint(equalTo: userProfilePicture.trailingAnchor, constant: 10),
commentTextField.centerYAnchor.constraint(equalTo: userProfilePicture.centerYAnchor),
commentTextField.heightAnchor.constraint(equalToConstant: 40),
commentTextField.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10),
])
}
override var intrinsicContentSize: CGSize {
return CGSize.zero
}
#objc func didTapPostButton() {
guard let text = commentTextField.text else {
return
}
commentTextField.resignFirstResponder()
delegate?.postButtonTapped(commentText: text)
}
}
I've spent days trying to google a fix for that but nothing helps.
There were posts saying they were able to fix something similar by setting customView's bottom constraint to a safe area with the following method:
override func didMoveToWindow() {
if #available(iOS 11.0, *) {
if let window = window {
let bottomAnchor = bottomAnchor.constraint(lessThanOrEqualToSystemSpacingBelow: window.safeAreaLayoutGuide.bottomAnchor, multiplier: 1.0)
bottomAnchor.isActive = true
}
}
}
But when I use it, AutoLayout starts complaining.
UPDATE: I did what HangarRash recommended, changed CommentAccessoryView from UIView to UIInputView and centering profileImageView and textField to view itself and not to safe area. Now it's a little bit better, but seems to ignore safe area, inputAccessoryView should be above Home indicator but lies beneath it instead. Looking at last cell in TableView and Scroll indicator, it seems like TableView also isn't aware of inputAccessoryView and goes under it.

Swift UICollectionViewCell UIlabel issue

I am writing a calendar, and each day is a cell, each cell has a Rounded UILabel in contentView, but I don't know why is there the little black border on each cell
Calendar image
In 3d View 3d preview
class CalendarCell: UICollectionViewCell {
static var identifier: String = "DayCell"
let dayLabel: UILabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
self.setUpUI()
self.contentView.addSubview(dayLabel)
}
private func setUpUI() {
dayLabel.text = nil
dayLabel.sizeToFit()
dayLabel.backgroundColor = .white
//dayLabel.layer.borderWidth = 0.5
dayLabel.textColor = .black
dayLabel.textAlignment = .center
dayLabel.clipsToBounds = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
dayLabel.frame = self.contentView.frame
dayLabel.layer.cornerRadius = dayLabel.frame.width / 2
}
override func prepareForReuse() {
super.prepareForReuse()
setUpUI()
}
I'm not sure what's causing the problem but I'm pretty sure you can fix it and achieve the same behavior by changing your code to this:
let collectionViewCellWidth: CGFLoat = 150 // or whatever you want. You'd define this in the file with your custom flow layout or wherever your give the cell size to the collectionView.
class CalendarCell: UICollectionViewCell {
static let identifier = "DayCell" // type inference doesn't need the annotations on these two
let dayLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setUpUI()
}
private func setUpUI() {
contentView.layer.cornerRadius = collectionViewCellWidth / 2
contentView.clipsToBounds = true
contentView.backgroundColor = .white // or orange, whatever
dayLabel.text = nil
dayLabel.backgroundColor = .white
//dayLabel.layer.borderWidth = 0.5
dayLabel.textColor = .black
dayLabel.textAlignment = .center
dayLabel.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(dayLabel)
NSLayoutConstraint.activate([
dayLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
dayLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//override func layoutSubviews() {
// dayLabel.frame = self.contentView.frame
// dayLabel.layer.cornerRadius = dayLabel.frame.width / 2
//}
// also as your code currently is, you don't do anything in your setup function that needs to be redone when a cell is dequeued for reuse. Unless you were setting some unique information for a cell like its color or text. Just FYI
override func prepareForReuse() {
super.prepareForReuse()
setUpUI()
}
}

UITapGestureRecognizer for UILabel inside of a StackView

I have a Stack View with two labels, one of which once tapped, suppose to lead to another view.
Here the code of my UIView subclass where labels and a StackView are setup:
import UIKit
import SnapKit
class WelcomeView: UIView {
weak var coordinator: MainCoordinator?
private let imageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "backGroundImage")
imageView.contentMode = .scaleAspectFill
imageView.layer.masksToBounds = true
return imageView
}()
private let questionLabel: UILabel = {
let questionLabel = UILabel()
questionLabel.text = "Don't have an Account?"
questionLabel.font = UIFont(name: "Avenir Next Regular", size: 17)
questionLabel.textColor = .black
return questionLabel
}()
private let signUpLabel: UILabel = {
let signUpLabel = UILabel()
let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.signUpTapped(tapGesture:)))
signUpLabel.text = "Sign Up"
signUpLabel.font = UIFont(name: "Avenir Next Regular", size: 17)
signUpLabel.textColor = .black
signUpLabel.highlightedTextColor = .link
signUpLabel.isHighlighted = true
signUpLabel.isUserInteractionEnabled = true
signUpLabel.addGestureRecognizer(tap)
return signUpLabel
}()
lazy var signUpstackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [questionLabel, signUpLabel])
stackView.axis = .horizontal
stackView.alignment = .fill
stackView.distribution = .fill
stackView.spacing = 8
stackView.isUserInteractionEnabled = true
return stackView
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubViews()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
addSubViews()
}
func addSubViews() {
self.backgroundColor = .white
self.addSubview(imageView)
self.addSubview(btnSignIn)
self.addSubview(signUpstackView)
setConstraints()
}
func setConstraints() {
imageView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
btnSignIn.snp.makeConstraints { (make) in
make.height.equalTo(60)
make.bottomMargin.equalTo(-50)
make.leftMargin.equalTo(28)
make.rightMargin.equalTo(-28)
}
signUpstackView.snp.makeConstraints { (make) in
make.height.equalTo(24)
make.centerX.equalTo(self)
make.top.equalTo(btnSignIn).offset(70)
}
}
}
I added UITapGestureRecognizer in signUpLabel.
And here is the code from my ViewController containing my IBAction function signUpTapped which is specified in UITapGestureRecognizer:
class ViewController: UIViewController {
var welcomeView = WelcomeView()
override func loadView() {
view = welcomeView
}
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func signUpTapped(tapGesture:UITapGestureRecognizer) {
print("Tapped")
}
}
For some reason nothing is happened when I try to click on my SignUp Label. Is this an issue because my UILabel is inside of a StackView?
You need
Sol 1
let tap = UITapGestureRecognizer(target: self, action: #selector(self.signUpTapped(tapGesture:)))
then inside the view class
weak var delegate:ViewController?
#objc func signUpTapped(tapGesture:UITapGestureRecognizer) {
print("Tapped")
delegate?.tapped()
}
var welcomeView = WelcomeView()
override func loadView() {
welcomeView.delegate = self
view = welcomeView
}
func tapped(){}
Sol 2
weak var delegate:ViewController?
init(frame: CGRect,delegate:ViewController) {
super.init(frame: frame)
self.delegate = delegate
addSubViews()
}
With
let tap = delegate(target:delegate!, action: #selector(delegate!.signUpTapped(tapGesture:)))
and inside the vc
#objc func signUpTapped(tapGesture:UITapGestureRecognizer) {
print("Tapped")
}

Struggling to show data in a label between custom classes and the view controller

This is in my viewController
import UIKit
import SnapKit
class ViewController: UIViewController {
var numberInCircleView: NumberInCircleView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.setupViews()
}
func setupViews() {
self.numberInCircleView = NumberInCircleView(frame: .zero)
self.view.addSubview(self.numberInCircleView)
self.numberInCircleView.snp.makeConstraints { (make) in
make.leading.equalTo(20)
make.bottom.equalTo(-40)
make.width.equalTo(60)
make.height.equalTo(60)
}
self.numberInCircleView.numberLabel = 5
}
}
The following is in my custom class.
import Foundation
import UIKit
class NumberInCircleView: UIView {
var numberLabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
self.setupViews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupViews()
}
func setupViews() {
self.numberLabel = UILabel(frame: .zero)
self.addSubview(self.numberLabel)
self.numberLabel.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
self.layer.cornerRadius = 30
self.backgroundColor = UIColor.red
self.layer.borderWidth = 1
self.layer.borderColor = UIColor.black.cgColor
self.numberLabel.text = ""
self.numberLabel.textAlignment = .center
self.numberLabel.textColor = UIColor.white
self.numberLabel.adjustsFontSizeToFitWidth = true
self.numberLabel.minimumScaleFactor = 0.5
}
This line in my ViewController
self.numberInCircleView.numberLabel = 5
Should set the label as 5. I'm not sure how I call between views to do this? Could you please advise? Some online tutorials that offer me more info on this would be much appreciated too.
It seems like you want to set the value 5 to the label but here you are missing the proper syntax, the numberLabel is a UILabel instance and has the property text of type String if you want to set the text to the label you have to assign some string like this:
self.numberInCircleView.numberLabel.text = "5"
I'm not sure how I call between views to do this
You can call any method on any object till you have a reference to it.
self.numberInCircleView.numberLabel = 5 this is compile time error , as you can't assign 5 to type of "UILabel"
Replace
self.numberInCircleView.numberLabel = 5
with
self.numberInCircleView.numberLabel.text = "5"

Programmatically access members of a UIView subclass in a UIViewController subclass

I want to put all subview properties and my subview setup code in a UIView subclass and load that into my UIViewController subclass using loadView(). Then access the UIView subclass members without casting the view property of UIViewController all the time.
This is my UIView subclass AwesomeClass
class AwesomeView: UIView {
lazy var testView:UIView = {
let view = UIView()
view.backgroundColor = UIColor.red
self.addSubview(view)
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
testView.frame = CGRect(x: 10
, y: 10
, width: self.bounds.size.width - 20
, height: 100)
}
}
And my UIViewController subclass AwesomeViewController
class AwesomeViewController: UIViewController {
override func loadView() {
let view = AwesomeView()
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I could do something like:
var subclassedView:AwesomeView {
get {
return self.view as! AwesomeView
}
}
and
subclassedView.testView.backgroundColor = UIColor.blue
But is there a way to call testView directly with self.view in the AwesomeViewController?
Edit:
What I am looking for is Covariant return type in swift.
You can you something like this:
Instantiate an instance of AwesomeViewController in AwesomeView
class AwesomeView: UIView {
var exampleColorVariable:UIColor?
//here you instantiate your view controller
var awesomeViewController = AwesomeViewController()
lazy var testView:UIView = {
let view = UIView()
view.backgroundColor = UIColor.red
self.addSubview(view)
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
testView.frame = CGRect(x: 10
, y: 10
, width: self.bounds.size.width - 20
, height: 100)
}
}
then you can access any method in AwesomeView changing a little bit your code
class AwesomeViewController: UIViewController {
lazy var awesomeView: AwesomeView = {
let view = AwesomeView()
view.awesomeViewController = self
return view
}()
func setupView() {
view.addSubview(awesomeView)
// your constraints here
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupView()
// NOW YOU CAN ACCESS ANY METHOD IN YOUR VIEW awesomeView.yourFunction()
// or you access that variable
awesomeView.exampleColorVariable = .red // you can now omit UIColor in swift3
}
}