How to segue smoothly from UITableViewController to UIViewController programmatically - ios

I'm not using storyboard and I'm doing everything programmatically.
So my problem is that when try to run the app on my phone (This does not happen when I use the simulator in Xcode), and when I use the following method...
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let destinationVC = ThirdViewController()
destinationVC.selectedExercise = exercises![indexPath.row]
self.navigationController?.pushViewController(destinationVC, animated: true)
}
...to transition from a UITableView to a UIView, the contents of the View transition normally, but the background freezes for a second before transitioning to the UIView.
What's causing this and how can I fix this?
Here is the code for the viewController that I'm transitioning to.
import UIKit
import RealmSwift
class ThirdViewController: UIViewController, UITextViewDelegate {
let realm = try! Realm()
var stats : Results<WeightSetsReps>?
var weightTextField = UITextField()
var weightLabel = UILabel()
var notesTextView = UITextView()
var repsTextField = UITextField()
var repsLabel = UILabel()
var timerImage = UIImageView()
var nextSet = UIButton()
var nextExcersise = UIButton()
var selectedExercise : Exercises? {
didSet{
loadWsr()
}
}
//MARK: - ViewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
notesTextView.delegate = self
timeClock()
navConAcc()
labelConfig()
setTextFieldConstraints()
setImageViewConstraints()
setTextViewConstraints()
setButtonConstraints()
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tap)
}
//MARK: - UILabel
func labelConfig(){
weightTextField.placeholder = "Total weight..."
weightTextField.layer.borderWidth = 1
weightTextField.backgroundColor = .white
weightTextField.layer.cornerRadius = 25
weightTextField.layer.borderColor = UIColor.lightGray.cgColor
weightLabel.text = " Weight (lbs): "
weightLabel.textColor = .black
weightTextField.leftView = weightLabel
weightTextField.leftViewMode = .always
repsTextField.placeholder = "Number of Reps..."
repsTextField.layer.borderWidth = 1
repsTextField.backgroundColor = .white
repsTextField.layer.cornerRadius = 25
repsTextField.layer.borderColor = UIColor.lightGray.cgColor
repsLabel.text = " Repetitions: "
repsLabel.textColor = .black
notesTextView.layer.borderWidth = 1
notesTextView.backgroundColor = .white
notesTextView.layer.cornerRadius = 25
notesTextView.layer.borderColor = UIColor.lightGray.cgColor
notesTextView.text = " Notes..."
notesTextView.textColor = UIColor.lightGray
notesTextView.returnKeyType = .done
repsTextField.leftView = repsLabel
repsTextField.leftViewMode = .always
nextSet.layer.borderWidth = 1
nextSet.backgroundColor = .white
nextSet.layer.cornerRadius = 25
nextSet.layer.borderColor = UIColor.lightGray.cgColor
nextSet.setTitle("Next Set", for: .normal)
nextSet.setTitleColor(.black, for: .normal)
nextSet.addTarget(self, action: #selector(addNewSet), for: .touchUpInside)
nextExcersise.layer.borderWidth = 1
nextExcersise.backgroundColor = .white
nextExcersise.layer.cornerRadius = 25
nextExcersise.layer.borderColor = UIColor.lightGray.cgColor
nextExcersise.setTitle("Next Exercise", for: .normal)
nextExcersise.setTitleColor(.black, for: .normal)
[weightTextField, repsTextField, notesTextView, nextSet, nextExcersise].forEach{view.addSubview($0)}
}
//MARK: - TextView Delegates
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.text == " Notes..." {
textView.text = ""
textView.textColor = UIColor.black
}
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == "\n" {
textView.resignFirstResponder()
}
return true
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text == ""{
notesTextView.text = " Notes..."
notesTextView.layer.borderColor = UIColor.lightGray.cgColor
}
}
//MARK: - Dismiss Keyboard Function
#objc func dismissKeyboard(){
view.endEditing(true)
}
//MARK: - TextField Constraints
func setTextFieldConstraints(){
weightTextField.anchor(top: view.safeAreaLayoutGuide.topAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor,padding: .init(top: 20, left: 40, bottom: 0, right: -40), size: .init(width: 0, height: 50))
repsTextField.anchor(top: weightTextField.bottomAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor, padding: .init(top: 30, left: 40, bottom: 0, right: -40) ,size: .init(width: 0, height: 50))
}
//MARK: - UIButton Functions
#objc func addNewSet(){
print("It Works")
}
//MARK: - UIButton Constraints
func setButtonConstraints(){
nextSet.anchor(top: nil, leading: view.leadingAnchor, bottom: view.safeAreaLayoutGuide.bottomAnchor, trailing: nil, padding: .init(top: 0, left: 40, bottom: 0, right: -150), size: .init(width: 120, height: 70))
nextExcersise.anchor(top: nil, leading: nextSet.trailingAnchor, bottom: nextSet.bottomAnchor, trailing: view.trailingAnchor, padding: .init(top: 0, left: 85, bottom: 0, right: -40), size: .init(width: 120, height: 70))
}
//MARK: - ImageView Constraints
func setImageViewConstraints(){
timerImage.anchor(top: repsTextField.bottomAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor, padding: .init(top: 40, left: 0, bottom: 0, right: 0), size: .init(width: 100, height: 100))
}
//MARK: - TextView Constraints
func setTextViewConstraints(){
notesTextView.anchor(top: timerImage.bottomAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor, padding: .init(top: 40, left: 40, bottom: 0, right: -40), size: .init(width: 100, height: 110))
}
//MARK: - Navigation Bar Setup
func navConAcc(){
navigationItem.title = selectedExercise?.exerciseName
navigationController?.navigationBar.prefersLargeTitles = true
}
//MARK: - Stopwatch
func timeClock(){
let image1 = UIImage(named: "stopwatch")
timerImage = UIImageView(image: image1)
timerImage.contentMode = .scaleAspectFit
self.view.addSubview(timerImage)
}
//MARK: - Load Data
func loadWsr() {
stats = selectedExercise?.wsr.sorted(byKeyPath: "sets", ascending: true)
}
//MARK: - Save Data
func save(wsr : WeightSetsReps){
do {
try realm.write {
realm.add(wsr)
}
} catch {
print("Error saving wsr data \(error)")
}
}
}
extension UIView {
func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero){
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: padding.top).isActive = true
}
if let leading = leading {
leadingAnchor.constraint(equalTo: leading, constant: padding.left).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: padding.bottom).isActive = true
}
if let trailing = trailing {
trailingAnchor.constraint(equalTo: trailing, constant: padding.right).isActive = true
}
if size.width != 0 {
widthAnchor.constraint(equalToConstant: size.width).isActive = true
}
if size.height != 0 {
heightAnchor.constraint(equalToConstant: size.height).isActive = true
}
}
}

You're not implementing programmatic view controllers correctly. A programmatically-created view controller does all of its view building in loadView(), not viewDidLoad(). Therefore, add all of the view controller's subviews in loadView() (without calling super.loadView()). Then use viewDidLoad() (with calling super.viewDidLoad()) to do post-view work, like adding timers, notification observers, etc. I suspect the lagging is caused by an incorrectly-configured lifecycle.
It also appears you're relatively new to iOS or Swift development and so I would strongly suggest you do not use extensions, especially on UIView for auto layout. Learn how it all works first before you begin extending things. The process for programmatic auto layout is:
// adjust parameters first, like color, delegate, etc.
someView.translatesAutoresizingMaskIntoConstraints = false // set resizing to false before adding as a subview
view.addSubview(someView) // add as a subview
someView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16).isActive = true // add constraints

Related

UIButton Not Responding to Touch Events

Problem
I am trying to do everything programmatically, so I have created this ResultController. For some reason, two of my buttons work but the other three don't work. The program doesn't even notice the touch-up event. I have read many posts on this topic but none of the solutions worked for me.
The two buttons that work are the language button and the swap button, which are embedded in a stack view towards the top of the screen. The 3 buttons that don't work are the soundButton, loopButton, and addFlashcardButton on the result card shown in the image below.
Question
What do I need to do such that these buttons respond to touch events?
Here is the code for this viewController, as well as a picture of the result. I will post the important parts and the whole code for reference. The Buttons are laid out in two functions, setupResultsView() and setupScrollView():
Button Instantiation
var soundButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "speaker.3.fill"), for: .normal)
button.backgroundColor = .white
button.tintColor = .black
button.addTarget(self, action: #selector(soundButtonPressed), for: .touchUpInside)
return button
}()
var loopButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "repeat"), for: .normal)
button.backgroundColor = .white
button.tintColor = .black
button.addTarget(self, action: #selector(loopButtonPressed), for: .touchUpInside)
return button
}()
var addFlashcardButton: UIButton = {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
button.setImage(UIImage(systemName: "plus"), for: .normal)
button.backgroundColor = K.Colors.purple
button.tintColor = .white
button.roundCorners(cornerRadius: 15)
button.addTarget(self, action: #selector(addFlashcardButtonPressed), for: .touchUpInside)
return button
}()
Selector Functions
#objc func soundButtonPressed() {
print("soundButton")
}
#objc func loopButtonPressed() {
print("loopButton")
}
#objc func addFlashcardButtonPressed() {
print("adding flashcard")
}
Complete Code
import UIKit
class ResultsController: UIViewController {
//MARK: - Info
let cellID = "SoundItOutCell"
//MARK: - Views
var centerTitle: UILabel = {
let label = UILabel(frame: CGRect(x: 10, y: 0, width: 50, height: 40))
label.backgroundColor = K.Colors.purple
label.font = UIFont.boldSystemFont(ofSize: 30)
label.text = "Aura"
label.numberOfLines = 2
label.textColor = .white
label.textAlignment = .center
return label
}()
var languageBarView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.setUnderlineStyle(color: K.Colors.purple)
return view
}()
var langStackView: UIStackView = {
let stackView = UIStackView()
stackView.backgroundColor = .white
stackView.alignment = .center
stackView.distribution = .fillEqually
return stackView
}()
var languageButton: UIButton = {
let button = UIButton()
button.setTitle("Japanese", for: .normal)
button.addTarget(self, action: #selector(languageButtonPressed), for: .touchUpInside)
button.backgroundColor = .white
button.setTitleColor(UIColor.systemBlue, for: .normal)
return button
}()
var swapButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "repeat"), for: .normal)
button.addTarget(self, action: #selector(swapButtonPressed), for: .touchUpInside)
button.tintColor = .black
button.backgroundColor = .white
return button
}()
var englishLabel: UILabel = {
let label = UILabel()
label.text = "English"
label.textColor = .black
label.backgroundColor = .white
label.textAlignment = .center
return label
}()
var textViewBackgroundView = UIView()
var textView: UITextView = {
let textView = UITextView()
textView.returnKeyType = .search
textView.text = "Enter text"
textView.textColor = .lightGray
textView.font = .systemFont(ofSize: 20)
textView.textContainerInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 50)
return textView
}()
var cancelButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "multiply"), for: .normal)
button.backgroundColor = .white
button.tintColor = .black
button.addTarget(self, action: #selector(cancelButtonPressed), for: .touchUpInside)
button.isHidden = true
return button
}()
var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.backgroundColor = K.Colors.lightGrey
return scrollView
}()
var contentView: UIView = {
let view = UIView()
view.backgroundColor = K.Colors.lightGrey
return view
}()
var resultCardView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.roundCorners(cornerRadius: 10)
return view
}()
var topLabel = UILabel()
var bottomLabel = UILabel()
var soundButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "speaker.3.fill"), for: .normal)
button.backgroundColor = .white
button.tintColor = .black
button.addTarget(self, action: #selector(soundButtonPressed), for: .touchUpInside)
return button
}()
var loopButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "repeat"), for: .normal)
button.backgroundColor = .white
button.tintColor = .black
button.addTarget(self, action: #selector(loopButtonPressed), for: .touchUpInside)
return button
}()
var lineDividerView: UIView = {
let view = UIView()
view.backgroundColor = K.Colors.purple
return view
}()
var addFlashcardButton: UIButton = {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
button.setImage(UIImage(systemName: "plus"), for: .normal)
button.backgroundColor = K.Colors.purple
button.tintColor = .white
button.roundCorners(cornerRadius: 15)
button.addTarget(self, action: #selector(addFlashcardButtonPressed), for: .touchUpInside)
return button
}()
// Background Views
let resultBackgroundView = UIView ()
let addFlashcardBackgroundView = UIView()
//MARK: - Class Functions
override func viewDidLoad() {
super.viewDidLoad()
setupView()
view.backgroundColor = K.Colors.lightGrey
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setupShadows()
}
}
extension ResultsController {
//MARK:- View Setup Functions
func setupView() {
self.setupToHideKeyboardOnTapOnView()
setupNavBar()
setupLanguageSelectionView()
setupTextView()
setupScrollView()
}
func setupShadows() {
textViewBackgroundView.setShadow(color: UIColor.black, opacity: 0.3, offset: .init(width: 0, height: 3), radius: 2)
resultBackgroundView.setShadow(color: .black, opacity: 0.3, offset: CGSize(width: 5, height: 5), radius: 2, cornerRadius: 10)
addFlashcardBackgroundView.setShadow(color: .black, opacity: 0.5, offset: CGSize(width: 2, height: 2), radius: 2, cornerRadius: 15)
}
func setupNavBar() {
// Add Center Title
self.navigationItem.titleView = centerTitle
// Add userbutton
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "person"),
style: .plain,
target: self,
action: #selector(profileButtonTapped))
// Make bar color purple, and buttons white
self.navigationController?.navigationBar.tintColor = .white
self.navigationController?.navigationBar.barTintColor = K.Colors.purple
}
func setupLanguageSelectionView() {
// Add Language Bar View
view.addSubview(languageBarView)
languageBarView.anchor(top: view.safeAreaLayoutGuide.topAnchor,
bottom: nil, leading: view.leadingAnchor,
trailing: view.trailingAnchor,
height: 60,
width: nil)
// Add Stack View
languageBarView.addSubview(langStackView)
langStackView.anchor(top: languageBarView.topAnchor,
bottom: languageBarView.bottomAnchor,
leading: languageBarView.leadingAnchor,
trailing: languageBarView.trailingAnchor,
height: nil,
width: nil,
padding: UIEdgeInsets(top: 0, left: 0, bottom: -2, right: 0))
// Add Language Button
langStackView.addArrangedSubview(languageButton)
// Add Swap Button
langStackView.addArrangedSubview(swapButton)
// Add English Label
langStackView.addArrangedSubview(englishLabel)
}
func setupTextView() {
// Add View
view.addSubview(textViewBackgroundView)
textViewBackgroundView.anchor(top: langStackView.bottomAnchor,
bottom: nil,
leading: view.leadingAnchor,
trailing: view.trailingAnchor,
height: 60,
width: nil,
padding: UIEdgeInsets(top: 2, left: 0, bottom: 0, right: 0))
// Add TextView
textView.delegate = self
view.addSubview(textView)
textView.anchor(top: textViewBackgroundView.topAnchor,
bottom: textViewBackgroundView.bottomAnchor,
leading: textViewBackgroundView.leadingAnchor,
trailing: textViewBackgroundView.trailingAnchor,
height: nil,
width: nil,
padding: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0))
// Add X Button
view.addSubview(cancelButton)
cancelButton.anchor(top: textView.topAnchor,
bottom: nil,
leading: nil,
trailing: textView.trailingAnchor,
height: 40,
width: 40,
padding: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0))
}
func setupScrollView() {
// Add scroll view and content view
view.addSubview(scrollView)
view.sendSubviewToBack(scrollView)
scrollView.addSubview(contentView)
// Anchor Scroll View
scrollView.anchorXCenter(top: textView.topAnchor,
bottom: view.safeAreaLayoutGuide.bottomAnchor,
centerAnchor: view.centerXAnchor,
width: view.widthAnchor,
height: nil)
// Anchor Content View
contentView.anchorXCenter(top: scrollView.topAnchor,
bottom: scrollView.bottomAnchor,
centerAnchor: scrollView.centerXAnchor,
width: scrollView.widthAnchor,
height: nil)
// Setup Results Background
contentView.addSubview(resultBackgroundView)
resultBackgroundView.anchorXCenter(top: contentView.topAnchor,
bottom: nil,
centerAnchor: contentView.centerXAnchor,
width: contentView.widthAnchor,
height: nil,
padding: UIEdgeInsets(top: 90, left: 0, bottom: 0, right: 0),
widthOffset: -40)
// Setup Results Card
resultBackgroundView.addSubview(resultCardView)
resultCardView.anchorXCenter(top: resultBackgroundView.topAnchor,
bottom: resultBackgroundView.bottomAnchor,
centerAnchor: resultBackgroundView.centerXAnchor,
width: resultBackgroundView.widthAnchor,
height: nil)
setupResultsView(resultCardView)
}
func setupResultsView(_ resultCardView: UIView) {
let searchInput = "Hello"
let searchOutput = "Yo what's up dog congratulations"
// Configure Top Label
topLabel.attributedText = NSAttributedString(string: searchInput)
topLabel.configureBasedOnInput()
// Configure Bottom Label
bottomLabel.attributedText = NSAttributedString(string: searchOutput)
bottomLabel.configureBasedOnInput()
// Add Flashcard Button to its Background View
addFlashcardBackgroundView.addSubview(addFlashcardButton)
addFlashcardButton.anchor(top: addFlashcardBackgroundView.topAnchor,
bottom: addFlashcardBackgroundView.bottomAnchor,
leading: addFlashcardBackgroundView.leadingAnchor,
trailing: addFlashcardBackgroundView.trailingAnchor,
height: nil,
width: nil)
// Add subviews to Result Card
resultCardView.addSubview(topLabel)
resultCardView.addSubview(bottomLabel)
resultCardView.addSubview(soundButton)
resultCardView.addSubview(loopButton)
resultCardView.addSubview(lineDividerView)
resultCardView.addSubview(addFlashcardBackgroundView)
// Autolayout Constraints For Result Card Subviews
topLabel.translatesAutoresizingMaskIntoConstraints = false
topLabel.topAnchor.constraint(equalTo: resultCardView.topAnchor, constant: 10).isActive = true
topLabel.centerXAnchor.constraint(equalTo: resultCardView.centerXAnchor, constant: 0).isActive = true
topLabel.leadingAnchor.constraint(greaterThanOrEqualTo: resultCardView.leadingAnchor, constant: 10).isActive = true
topLabel.trailingAnchor.constraint(lessThanOrEqualTo: resultCardView.trailingAnchor, constant: -10).isActive = true
lineDividerView.anchor(top: topLabel.bottomAnchor,
bottom: nil, leading: resultCardView.leadingAnchor,
trailing: resultCardView.trailingAnchor,
height: 1,
width: nil,
padding: UIEdgeInsets(top: 10, left: 0, bottom: 0, right: 0))
loopButton.anchor(top: lineDividerView.bottomAnchor,
bottom: nil,
leading: nil,
trailing: resultCardView.trailingAnchor,
height: nil,
width: nil,
padding: UIEdgeInsets(top: 10, left: 0, bottom: 0, right: -10))
soundButton.anchor(top: lineDividerView.bottomAnchor,
bottom: nil,
leading: nil,
trailing: loopButton.leadingAnchor,
height: nil,
width: nil,
padding: UIEdgeInsets(top: 10, left: 0, bottom: 0, right: -10))
bottomLabel.translatesAutoresizingMaskIntoConstraints = false
bottomLabel.topAnchor.constraint(equalTo: soundButton.bottomAnchor, constant: 10).isActive = true
bottomLabel.centerXAnchor.constraint(equalTo: resultCardView.centerXAnchor, constant: 0).isActive = true
bottomLabel.leadingAnchor.constraint(greaterThanOrEqualTo: resultCardView.leadingAnchor, constant: 10).isActive = true
bottomLabel.trailingAnchor.constraint(lessThanOrEqualTo: resultCardView.trailingAnchor, constant: -10).isActive = true
addFlashcardBackgroundView.anchor(top: bottomLabel.bottomAnchor,
bottom: resultCardView.bottomAnchor,
leading: nil,
trailing: resultCardView.trailingAnchor,
height: 30,
width: 30,
padding: UIEdgeInsets(top: 10, left: 0, bottom: -10, right: -10))
}
//MARK:- Selector Functions
#objc func profileButtonTapped() {
print(0)
}
#objc func languageButtonPressed() {
print(1)
}
#objc func swapButtonPressed() {
print(2)
}
#objc func cancelButtonPressed() {
textView.text = ""
textView.textColor = .lightGray
textView.text = "Enter text"
cancelButton.isHidden = true
}
#objc func soundButtonPressed() {
print("soundButton")
}
#objc func loopButtonPressed() {
print("loopButton")
}
#objc func addFlashcardButtonPressed() {
print("adding flashcard")
}
}
Output: Circled Buttons are ones that don't work

mapKit error as Value of type 'UIImageView' has no member 'anchor'

var imageView: UIImageView {
let iv = UIImageView()
iv.contentMode = .scaleAspectFit
iv.image = UIImage(named: "locationPin")
return iv
}
//MARK:- Init
override func viewDidLoad() {
super.viewDidLoad()
configureViewComponents()
}
//MARK:- Helper function
func configureViewComponents() {
view.backgroundColor = .white
view.addSubview(imageView)
// below I am getting error as
Value of type 'UIImageView' has no member 'anchor'
imageView.anchor(top: view.topAnchor, left: nil, bottom: nil, right: nil, paddingTop: 140, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 200, height: 200)
}
}
I tried to implement the popup enable location screen programmatically with an imageview but I am getting the error as "Value of type 'UIImageView' has no member 'anchor'"
You should have
extension UIView {
func anchor(} {}
}
Found Here
imageViewtranslatesAutoresizingMaskIntoConstraints = false
view.addSubview(imageView)
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 140.0 ),
imageView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 10 ),
imageView.widthAnchor.constraint(equalToConstant:200),
imageView.heightAnchor.constraint(equalToConstant: 200)
])
With extension
enum ConstraintType {
case top, leading, trailing, bottom, width, height
}
extension UIView {
func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero) {
//translate the view's autoresizing mask into Auto Layout constraints
translatesAutoresizingMaskIntoConstraints = false
var constraints: [ConstraintType : NSLayoutConstraint] = [:]
if let top = top {
constraints[.top] = topAnchor.constraint(equalTo: top, constant: padding.top)
}
if let leading = leading {
constraints[.leading] = leadingAnchor.constraint(equalTo: leading, constant: padding.left)
}
if let bottom = bottom {
constraints[.bottom] = bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom)
}
if let trailing = trailing {
constraints[.trailing] = trailingAnchor.constraint(equalTo: trailing, constant: -padding.right)
}
if size.width != 0 {
constraints[.width] = widthAnchor.constraint(equalToConstant: size.width)
}
if size.height != 0 {
constraints[.height] = heightAnchor.constraint(equalToConstant: size.height)
}
let constraintsArray = Array<NSLayoutConstraint>(constraints.values)
NSLayoutConstraint.activate(constraintsArray)
}
}
class ViewController: UIViewController {
let imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFit
iv.image = UIImage(named: "locationPin")
return iv
}()
// MARK: - View Life Cycle Methods
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(imageView)
imageView.anchor(top:view.topAnchor, leading: view.leadingAnchor, bottom: nil, trailing: nil, padding: UIEdgeInsets.init(top: 140, left: 0, bottom: 0, right: 0), size: CGSize.init(width: 200, height: 200))
}
}

Unable to activate constraint with anchors while adding a textfield

A while ago I started creating my screens via the storyboard.
Recently I have been convinced doing it programmatically.
Currently I am running into the following error:
Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to activate constraint with anchors and because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal.'
import UIKit
class ViewController: UIViewController {
var emailTextField: UITextField {
let tf = UITextField()
tf.placeholder = "email";
tf.backgroundColor = UIColor(white: 0, alpha: 0.03)
tf.borderStyle = .roundedRect
tf.font = UIFont.systemFont(ofSize: 14)
return tf
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(emailTextField)
emailTextField.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 40, paddingLeft: 20, paddingBottom: 0, paddingRight: 20, width: 0, height: 40)
}
}
I have created a function to anchor my elements in Extensions.swift:
import UIKit
extension UIView {
func anchor(top: NSLayoutYAxisAnchor?, left: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, right: NSLayoutXAxisAnchor?, paddingTop: CGFloat, paddingLeft: CGFloat, paddingBottom: CGFloat, paddingRight: CGFloat, width: CGFloat, height: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
self.topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
}
if let left = left {
self.leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
}
if let bottom = bottom {
self.bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
}
if let right = right {
self.rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
}
if width != 0 {
widthAnchor.constraint(equalToConstant: width).isActive = true
}
if height != 0 {
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
}
In addition, I deleted the Application Scene Manifest in Info.plist.
I want my project to support the upcoming iOS versions starting at 13.0.
Nevertheless I don't want to use the new framework SwiftUI.
Are there any specific things to watch out for (e.g. Scene Delegate)?
Thank you in advance!
You need to change this
var emailTextField: UITextField {
let tf = UITextField()
tf.placeholder = "email";
tf.backgroundColor = UIColor(white: 0, alpha: 0.03)
tf.borderStyle = .roundedRect
tf.font = UIFont.systemFont(ofSize: 14)
return tf
}
into this
var emailTextField: UITextField = {
let tf = UITextField()
tf.placeholder = "email";
tf.backgroundColor = UIColor(white: 0, alpha: 0.03)
tf.borderStyle = .roundedRect
tf.font = UIFont.systemFont(ofSize: 14)
return tf
}()
In current version emailTextField is computed property, so, every time you access it compiler create a new instance (it works like function, not variable).
Textfield was unable to get its parent view while you were adding its constraint. Below code works for me. I passed current view as a param to anchor() method and add emailTextField as a subview there.
class ViewController: UIViewController {
var emailTextField:UITextField = {
let tf = UITextField()
tf.placeholder = "email";
tf.backgroundColor = UIColor(white: 0, alpha: 0.03)
tf.borderStyle = .roundedRect
tf.font = UIFont.systemFont(ofSize: 14)
return tf
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
emailTextField.anchor(top: view.safeAreaLayoutGuide.topAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 40, paddingLeft: 20, paddingBottom: 0, paddingRight: 20, width: 0, height: 40, view:view)
}
}
extension UIView {
func anchor(top: NSLayoutYAxisAnchor?, left: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, right: NSLayoutXAxisAnchor?, paddingTop: CGFloat, paddingLeft: CGFloat, paddingBottom: CGFloat, paddingRight: CGFloat, width: CGFloat, height: CGFloat, view:UIView) {
translatesAutoresizingMaskIntoConstraints = false
view.addSubview(self)
if let top = top {
self.topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
}
if let left = left {
self.leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
}
if let bottom = bottom {
self.bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
}
if let right = right {
self.rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
}
if width != 0 {
widthAnchor.constraint(equalToConstant: width).isActive = true
}
if height != 0 {
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
}

How to properly ensure a custom UITableViewCell can be reused

I have a UITableViewController that is rendering out a custom UITableViewCell'.
This cells are related to chat messages, as such the config is almost identical, apart from how the elements are constrained.
bot cell is: avatar > message
user cell is message < avatar
I was hoping to combine these in a single custom cell and simply switch on an origin property on the model, allowing me to choose which constraints I am applying.
This worked for 5 or 6 messages, until however I ran a test with 30 messages and some cells started to inherit both sets of anchors or even just random properties that should be assigned to the other cell.
I can see the errors suggest the constraints are invalid and I believe this is due to the cell not being prepared for reuse correctly.
(
"<NSLayoutConstraint:0x600002533930 UIImageView:0x7fb401514d40.leading == UILayoutGuide:0x600003f18e00'UIViewLayoutMarginsGuide'.leading (active)>",
"<NSLayoutConstraint:0x600002526990 UITextView:0x7fb40200a200'I am a Person.'.leading == UILayoutGuide:0x600003f18e00'UIViewLayoutMarginsGuide'.leading + 15 (active)>",
"<NSLayoutConstraint:0x6000025271b0 UITextView:0x7fb40200a200'I am a Person.'.trailing == UIImageView:0x7fb401514d40.leading - 15 (active)>"
)
ChatMessageCell
class ChatMessageCell: UITableViewCell {
fileprivate var content: ChatMessage? {
didSet {
guard let text = content?.text else { return }
messageView.text = text
guard let origin = content?.origin else { return }
setupSubViews(origin)
}
}
fileprivate var messageAvatar: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.layer.cornerRadius = 35 / 2
imageView.layer.masksToBounds = true
return imageView
}()
fileprivate var messageView: UITextView = {
let textView = UITextView()
textView.isScrollEnabled = false
textView.isSelectable = false
textView.sizeToFit()
textView.layoutIfNeeded()
textView.contentInset = UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10)
textView.layer.cornerRadius = 10
textView.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMinYCorner]
textView.translatesAutoresizingMaskIntoConstraints = false
return textView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setContent(as content: ChatMessage) {
self.content = content
}
override func prepareForReuse() {
content = nil
}
}
extension ChatMessageCell {
fileprivate func setupSubViews(_ origin: ChatMessageOrigin) {
let margins = contentView.layoutMarginsGuide
[messageAvatar, messageView].forEach { v in contentView.addSubview(v) }
switch origin {
case .system:
messageAvatar.image = #imageLiteral(resourceName: "large-bot-head")
messageAvatar.anchor(
top: margins.topAnchor, leading: margins.leadingAnchor, size: CGSize(width: 35, height: 35)
)
messageView.anchor(
top: margins.topAnchor, leading: messageAvatar.trailingAnchor, bottom: margins.bottomAnchor, trailing: margins.trailingAnchor,
padding: UIEdgeInsets(top: 5, left: 15, bottom: 0, right: 15)
)
case .user:
let userContentBG = UIColor.hexStringToUIColor(hex: "00f5ff")
messageAvatar.image = UIImage.from(color: userContentBG)
messageAvatar.anchor(
top: margins.topAnchor, trailing: margins.trailingAnchor, size: CGSize(width: 35, height: 35)
)
messageView.layer.backgroundColor = userContentBG.cgColor
messageView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMinYCorner]
messageView.anchor(
top: margins.topAnchor, leading: margins.leadingAnchor, bottom: margins.bottomAnchor, trailing: messageAvatar.leadingAnchor,
padding: UIEdgeInsets(top: 5, left: 15, bottom: 0, right: 15)
)
}
}
}
ChatController
class ChatController: UITableViewController {
lazy var viewModel: ChatViewModel = {
let viewModel = ChatViewModel()
return viewModel
}()
fileprivate let headerView: UIView = {
let view = UIView(frame: .zero)
view.backgroundColor = .white
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.reloadData = { [weak self] in
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
configureViewHeader()
configureTableView()
registerTableCells()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.contentInset = UIEdgeInsets(top: 85, left: 0, bottom: 0, right: 0)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.history.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = viewModel.history[indexPath.row]
let cell = tableView.dequeueReusableCell(withClass: ChatMessageCell.self)
cell.setContent(as: item)
cell.layoutSubviews()
return cell
}
}
extension ChatController {
fileprivate func configureViewHeader() {
let margins = view.safeAreaLayoutGuide
view.addSubview(headerView)
headerView.anchor(
top: margins.topAnchor, leading: margins.leadingAnchor, trailing: margins.trailingAnchor,
size: CGSize(width: 0, height: 70)
)
}
fileprivate func configureTableView() {
tableView.tableFooterView = UIView()
tableView.allowsSelection = false
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 200
tableView.separatorStyle = .none
tableView.backgroundColor = UIColor.clear
}
fileprivate func registerTableCells() {
tableView.register(cellWithClass: ChatMessageCell.self)
}
}
An example of how the view changes on scroll can be seen here....
My Extensions are applied with
#discardableResult
func anchor(top: NSLayoutYAxisAnchor? = nil, leading: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, trailing: NSLayoutXAxisAnchor? = nil, padding: UIEdgeInsets = .zero, size: CGSize = .zero) -> AnchoredConstraints {
translatesAutoresizingMaskIntoConstraints = false
var anchoredConstraints = AnchoredConstraints()
if let top = top {
anchoredConstraints.top = topAnchor.constraint(equalTo: top, constant: padding.top)
}
if let leading = leading {
anchoredConstraints.leading = leadingAnchor.constraint(equalTo: leading, constant: padding.left)
}
if let bottom = bottom {
anchoredConstraints.bottom = bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom)
}
if let trailing = trailing {
anchoredConstraints.trailing = trailingAnchor.constraint(equalTo: trailing, constant: -padding.right)
}
if size.width != 0 {
anchoredConstraints.width = widthAnchor.constraint(equalToConstant: size.width)
}
if size.height != 0 {
anchoredConstraints.height = heightAnchor.constraint(equalToConstant: size.height)
}
[anchoredConstraints.top, anchoredConstraints.leading, anchoredConstraints.bottom, anchoredConstraints.trailing, anchoredConstraints.width, anchoredConstraints.height].forEach { $0?.isActive = true }
return anchoredConstraints
}
In your ChatMessageCell class, move:
[messageAvatar, messageView].forEach { v in contentView.addSubview(v) }
from setupSubViews(...) to init(...). With your current code, setupSubViews is being called every time you set the content. You only want to add the subviews to the cell's contentView when the cell is initialized.
From there, you need to check how you're adding constraints. If your .anchor(...) func / extension is first removing any existing constraints, you should be ok.
Edit:
Here is another option - you may find it easier to work with.
Since you have the same subviews, set up two arrays of constraints. Then activate or deactivate the appropriate set (as well as setting colors, corners, etc):
class ChatMessageCell: UITableViewCell {
fileprivate var content: ChatMessage? {
didSet {
guard let text = content?.text else { return }
messageView.text = text
guard let origin = content?.origin else { return }
setupSubViews(origin)
}
}
fileprivate var messageAvatar: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.layer.cornerRadius = 35 / 2
imageView.layer.masksToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
fileprivate var messageView: UITextView = {
let textView = UITextView()
textView.isScrollEnabled = false
textView.isSelectable = false
textView.sizeToFit()
textView.layoutIfNeeded()
textView.contentInset = UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10)
textView.layer.cornerRadius = 10
textView.translatesAutoresizingMaskIntoConstraints = false
return textView
}()
fileprivate var systemConstraints = [NSLayoutConstraint]()
fileprivate var userConstraints = [NSLayoutConstraint]()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func setContent(as content: ChatMessage) {
self.content = content
}
func commonInit() -> Void {
backgroundColor = .clear
let margins = contentView.layoutMarginsGuide
[messageAvatar, messageView].forEach { v in contentView.addSubview(v) }
systemConstraints = [
messageAvatar.leadingAnchor.constraint(equalTo: margins.leadingAnchor, constant: 0.0),
messageView.leadingAnchor.constraint(equalTo: messageAvatar.trailingAnchor, constant: 15.0),
messageView.trailingAnchor.constraint(equalTo: margins.trailingAnchor, constant: -15),
]
userConstraints = [
messageView.leadingAnchor.constraint(equalTo: margins.leadingAnchor, constant: 15.0),
messageAvatar.trailingAnchor.constraint(equalTo: margins.trailingAnchor, constant: 0.0),
messageAvatar.leadingAnchor.constraint(equalTo: messageView.trailingAnchor, constant: 15),
]
NSLayoutConstraint.activate([
// messageAvatar width/height/top is the same for each origin "type"
messageAvatar.topAnchor.constraint(equalTo: margins.topAnchor, constant: 0.0),
messageAvatar.heightAnchor.constraint(equalToConstant: 35),
messageAvatar.widthAnchor.constraint(equalToConstant: 35),
// messageView width/height/top is the same for each origin "type"
messageView.topAnchor.constraint(equalTo: margins.topAnchor, constant: 5.0),
messageView.bottomAnchor.constraint(equalTo: margins.bottomAnchor, constant: 0.0),
])
}
}
extension ChatMessageCell {
fileprivate func setupSubViews(_ origin: ChatMessageOrigin) {
switch origin {
case .system:
NSLayoutConstraint.deactivate(userConstraints)
NSLayoutConstraint.activate(systemConstraints)
messageView.backgroundColor = .white
messageAvatar.backgroundColor = .red
messageView.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMinYCorner]
default:
NSLayoutConstraint.deactivate(systemConstraints)
NSLayoutConstraint.activate(userConstraints)
messageView.backgroundColor = .cyan
messageAvatar.backgroundColor = .blue
messageView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMinYCorner]
}
}
}
Note: I'm using Swift 4.1, so there are a couple of syntax changes (but they'll be obvious).
When you have two different layouts of cells, having two different classes of cells would be another way to handle your issue.
ChatMessageCell
class ChatMessageCell: UITableViewCell {
fileprivate var content: ChatMessage? {
didSet {
guard let text = content?.text else { return }
messageView.text = text
}
}
//...
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = UIColor.clear
setupSubViews()
}
fileprivate func setupSubViews() {
[messageAvatar, messageView].forEach { v in contentView.addSubview(v) }
}
//...
}
class UserMessageCell: ChatMessageCell {
fileprivate override func setupSubViews() {
super.setupSubViews()
let margins = contentView.layoutMarginsGuide
let userContentBG = UIColor.hexStringToUIColor(hex: "00f5ff")
messageAvatar.image = UIImage.from(color: userContentBG)
messageAvatar.anchor(
top: margins.topAnchor, trailing: margins.trailingAnchor, size: CGSize(width: 35, height: 35)
)
messageView.layer.backgroundColor = userContentBG.cgColor
messageView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMinYCorner]
messageView.anchor(
top: margins.topAnchor, leading: margins.leadingAnchor, bottom: margins.bottomAnchor, trailing: messageAvatar.leadingAnchor,
padding: UIEdgeInsets(top: 5, left: 15, bottom: 0, right: 15)
)
}
}
class SystemMessageCell: ChatMessageCell {
fileprivate override func setupSubViews() {
super.setupSubViews()
let margins = contentView.layoutMarginsGuide
messageAvatar.image = #imageLiteral(resourceName: "large-bot-head")
messageAvatar.anchor(
top: margins.topAnchor, leading: margins.leadingAnchor, size: CGSize(width: 35, height: 35)
)
messageView.anchor(
top: margins.topAnchor, leading: messageAvatar.trailingAnchor, bottom: margins.bottomAnchor, trailing: margins.trailingAnchor,
padding: UIEdgeInsets(top: 5, left: 15, bottom: 0, right: 15)
)
}
}
ChatController
class ChatController: UITableViewController {
//...
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = viewModel.history[indexPath.row]
let cell: ChatMessageCell
switch item.origin {
case .system:
cell = tableView.dequeueReusableCell(withClass: SystemMessageCell.self)
case .user:
cell = tableView.dequeueReusableCell(withClass: UserMessageCell.self)
}
cell.setContent(as: item)
cell.layoutSubviews()
return cell
}
}
extension ChatController {
//...
fileprivate func registerTableCells() {
tableView.register(cellWithClass: SystemMessageCell.self)
tableView.register(cellWithClass: UserMessageCell.self)
}
}

Tableviews delegate eats button click

I got a large UITableViewCell representing a place. Elements of the cell are two Buttons starButton and infoButton. Both of them have a targetAction which never gets executed and I'm clueless why.. All touches are being eaten by the didSelectRowAt by the tableview. The thing that gets me so confused is the fact that It all worked well some hours ago and I did not change properties of the two buttons. Does someone has an idea what's going on?
The Hierarchy looks like this:
UITableView
OverviewTableCell UITableViewCell
dotsButton, thumbnailImageView UIButton , UIImageView
blackView UIView
titleLabel, descriptionLabel, starButton, infoButton UILabel , UIButton
Cell:
class OverviewTableViewCell: UITableViewCell, ReusableView {
lazy var dotsButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(named: GSSettings.UI.otherIcons.dotsHorizontal)?.withRenderingMode(.alwaysTemplate), for: .normal)
button.setImage(UIImage(named: GSSettings.UI.otherIcons.dotsVertical)?.withRenderingMode(.alwaysTemplate), for: .selected)
button.addTarget(self, action: #selector(seeMore), for: .touchUpInside)
button.fixHeightAndWidth(width: 28, height: 28)
button.tintColor = UIColor.gray
return button
}()
let thumbnailImageView: UIImageView = {
let imageview = UIImageView()
imageview.contentMode = .scaleAspectFill
imageview.clipsToBounds = true
imageview.image = UIImage(named: "testbild")
return imageview
}()
let blackView: UIImageView = {
let imageview = UIImageView()
imageview.backgroundColor = UIColor.black.withAlphaComponent(0.35)
return imageview
}()
let titleLabel : UILabel = {
let label = UILabel()
label.text = "Titel Titel"
label.font = GSSettings.UI.Fonts.helveticaMedium?.withSize(22)
label.textColor = UIColor.white
return label
}()
lazy var starButton: GSFavSpotButton = {
let button = GSFavSpotButton()
button.tintColor = UIColor.white//GSSettings.UI.Colors.nightOrange
button.addTarget(self, action: #selector(handleStarButton), for: .touchUpInside)
return button
}()
lazy var infoButton: GSInfoButton = {
let button = GSInfoButton()
button.tintColor = UIColor.white//GSSettings.UI.Colors.nightOrange
button.addTarget(self, action: #selector(handleInfoButton), for: .touchUpInside)
return button
}()
let addFriendView = GSInviteAFriendView()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.clipsToBounds = false
self.selectionStyle = .none
setupSubviews()
setupConstraints()
perform(#selector(printBounds), with: nil, afterDelay: 5)
}
func setupSubviews() {
self.addSubview(dotsButton)
self.addSubview(thumbnailImageView)
thumbnailImageView.addSubview(blackView)
blackView.addSubview(titleLabel)
blackView.addSubview(descriptionLabel)
blackView.addSubview(starButton)
blackView.addSubview(infoButton)
self.addSubview(addFriendView)
blackView.isHidden = true
descriptionLabel.isHidden = true
}
func setupConstraints() {
dotsButton.anchor(top: topAnchor, leading: nil, bottom: nil, trailing: trailingAnchor, paddingTop: 0, paddingLeading: 0, paddingBottom: 0, paddingTrailing: 16, width: 0, height: 0)
thumbnailImageView.anchor(top: dotsButton.bottomAnchor, leading: leadingAnchor, bottom: nil, trailing: trailingAnchor, paddingTop: 4, paddingLeading: 16, paddingBottom: 0, paddingTrailing: 16, width: 0, height: 0)
thumbnailImageView.heightAnchor.constraint(equalTo: thumbnailImageView.widthAnchor, multiplier: 9/16).isActive = true
blackView.fillSuperview(unsafeArea: true)
titleLabel.anchor(top: thumbnailImageView.topAnchor, leading: thumbnailImageView.leadingAnchor, bottom: nil, trailing: starButton.leadingAnchor, paddingTop: 8, paddingLeading: 8, paddingBottom: 0, paddingTrailing: 8, width: 0, height: 20)
infoButton.anchor(top: thumbnailImageView.topAnchor, leading: nil, bottom: nil, trailing: thumbnailImageView.trailingAnchor, paddingTop: 8, paddingLeading: 0, paddingBottom: 0, paddingTrailing: 8, width: 30, height: 30)
starButton.anchor(top: thumbnailImageView.topAnchor, leading: nil, bottom: nil, trailing: infoButton.leadingAnchor, paddingTop: 8, paddingLeading: 0, paddingBottom: 0, paddingTrailing: 4, width: 30, height: 30)
addFriendView.anchor(top: thumbnailImageView.bottomAnchor, leading: nil, bottom: nil, trailing: nil, paddingTop: -GSSettings.UI.Sizes.addFriendButtonSize/2 + 10, paddingLeading: 0, paddingBottom: 0, paddingTrailing: 0, width: GSSettings.UI.Sizes.addFriendButtonSize, height: GSSettings.UI.Sizes.addFriendButtonSize)
addFriendView.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0).isActive = true
}
#objc func seeMore() {
dotsButton.isSelected.toggle()
if dotsButton.isSelected {
blackView.isHidden = false
} else {
blackView.isHidden = false
}
layoutIfNeeded()
}
#objc func handleStarButton() {
starButton.isSelected.toggle()
}
#objc func handleInfoButton() {
infoButton.isSelected.toggle()
}
#objc func printBounds() {
print("::::")
print(thumbnailImageView.bounds)
print(infoButton.bounds)
print(starButton.bounds)
print("_____")
print(thumbnailImageView.frame)
print(infoButton.frame)
print(starButton.frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension UIView {
func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, paddingTop: CGFloat, paddingLeading: CGFloat, paddingBottom: CGFloat, paddingTrailing: CGFloat, width: CGFloat, height: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
}
if let leading = leading {
leadingAnchor.constraint(equalTo: leading, constant: paddingLeading).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
}
if let trailing = trailing {
trailingAnchor.constraint(equalTo: trailing, constant: -paddingTrailing).isActive = true
}
if width != 0 {
widthAnchor.constraint(equalToConstant: width).isActive = true
}
if height != 0 {
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
Tableview:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: OverviewTableViewCell.reuseIdentifier, for: indexPath) as! OverviewTableViewCell
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
delegate?.pushTo()
}
The printBounds() function in the cell shows that the frame and bounds are okay (I guess)
(0.0, 0.0, 382.0, 215.0)
(0.0, 0.0, 30.0, 30.0)
(0.0, 0.0, 30.0, 30.0)
(16.0, 32.0, 382.0, 215.0)
(344.0, 8.0, 30.0, 30.0)
(310.0, 8.0, 30.0, 30.0)
You can try
blackView.isUserInteractionEnabled = true
also add the subviews to
self.contentView and create the constraints of cell subviews with it

Resources