Multiple UITableViewCell problem in displaying - swift

I'm new in autolayout programmatically, and I have a problem with displaying 2 different cells in UITableView as below:
The first cell I want to show profiles picture and the username, then the menu options. But as screenshot the second section menu option is displaying behind the first section (user profile picture and username).
How to solve this problem?
UserTableViewCell:
class UserTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
layoutUI()
backgroundColor = .CustomGreen()
selectionStyle = .none
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var containerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .clear
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
lazy var userPhoto: UIImageView = {
let userPhoto = UIImageView()
userPhoto.translatesAutoresizingMaskIntoConstraints = false
userPhoto.layer.cornerRadius = userPhoto.frame.size.width / 2
userPhoto.clipsToBounds = true
return userPhoto
}()
lazy var username: UILabel = {
let username = UILabel()
username.textColor = .white
username.text = "Ahmed Abd Elaziz"
username.translatesAutoresizingMaskIntoConstraints = false
return username
}()
func setupContainerViewConstraints() {
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: topAnchor),
containerView.bottomAnchor.constraint(equalTo: bottomAnchor),
containerView.leadingAnchor.constraint(equalTo: leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: trailingAnchor)
])
}
func setupUserPhotoConstraints() {
NSLayoutConstraint.activate([
userPhoto.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
userPhoto.widthAnchor.constraint(equalToConstant: frame.width / 3),
userPhoto.heightAnchor.constraint(equalToConstant: frame.width / 3)
])
}
func setupUsernameConstraints() {
NSLayoutConstraint.activate([
username.topAnchor.constraint(lessThanOrEqualTo: userPhoto.bottomAnchor, constant: 16),
username.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
])
}
func addSubviews() {
addSubview(containerView)
containerView.addSubview(userPhoto)
containerView.addSubview(username)
}
func layoutUI() {
addSubviews()
setupContainerViewConstraints()
setupUserPhotoConstraints()
setupUsernameConstraints()
}
}
SideMenuOptionTableViewCell:
class SideMenuOptionTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
layoutUI()
backgroundColor = .CustomGreen()
selectionStyle = .none
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var containerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .clear
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
lazy var viewTitle: UILabel = {
let viewTitle = UILabel()
viewTitle.font = UIFont(name: "AvenirNext-Regular", size: 20)
viewTitle.textColor = .white
viewTitle.numberOfLines = 0
viewTitle.translatesAutoresizingMaskIntoConstraints = false
return viewTitle
}()
func setupContainerViewConstraints() {
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: topAnchor),
containerView.bottomAnchor.constraint(equalTo: bottomAnchor),
containerView.leadingAnchor.constraint(equalTo: leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: trailingAnchor)
])
}
func setupFoodTitle() {
NSLayoutConstraint.activate([
viewTitle.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
viewTitle.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16)
])
}
func addSubview() {
addSubview(containerView)
containerView.addSubview(viewTitle)
}
func layoutUI() {
addSubview()
setupContainerViewConstraints()
setupFoodTitle()
}
}
SideMenuTableViewController:
class SideMenuTableViewController: UITableViewController {
let viewControllers = ["Controller One", "Logout"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UserTableViewCell.self, forCellReuseIdentifier: "UserTableViewCell")
tableView.register(SideMenuOptionTableViewCell.self, forCellReuseIdentifier: "SideMenuOptionTableViewCell")
tableView.reloadData()
tableView.backgroundColor = .CustomGreen()
tableView.separatorStyle = .none
tableView.rowHeight = UITableView.automaticDimension
// tableView.estimatedRowHeight = 100
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return section == 0 ? 1 : viewControllers.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserTableViewCell", for: indexPath) as! UserTableViewCell
cell.userPhoto.image = UIImage(named: "ahmed")
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "SideMenuOptionTableViewCell", for: indexPath) as! SideMenuOptionTableViewCell
cell.viewTitle.text = viewControllers[indexPath.row]
return cell
}
}
}

The problem here is you are adding constraints according to the cells height and all the views are being adjusted in the cell. As the space for the views is too small they are overlapping the views of the next cell. You can fix this problem by increasing the size of the content view of the cell.
Add this line in init of your UserTableViewCell
let minHeight = 70
let minHeightConstraint = contentView.heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight)
minHeightConstraint.priority = UILayoutPriority(rawValue: 999)
minHeightConstraint.isActive = true

Related

Unable to simultaneously satisfy constraints. Adaptive cell height using UICollectionViewFlowLayout

I'm trying to make a news feed like app using UICollectionView with adaptive cell height. I found this tutorial and used the option "2. Solution for iOS 11+" for my code, which works perfectly fine. However, whenever I try to add more subviews to the cell and layout them as required, I get this error: "Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want."
Below is my code which gives me an error.
NewsFeedViewController:
import UIKit
class NewsFeedViewController: UIViewController {
var friends: [Friend] = []
var allPosts: [Post?] = []
var shuffledPosts: [Post?] = []
var collectionView: UICollectionView = {
let layout = NewsFeedFlowLayout()
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize // new
layout.scrollDirection = .vertical
let collection = UICollectionView(frame: .zero, collectionViewLayout: layout)
collection.backgroundColor = UIColor.systemRed
collection.isScrollEnabled = true
collection.contentInsetAdjustmentBehavior = .always
collection.translatesAutoresizingMaskIntoConstraints = false
return collection
}()
let cellID = "NewsFeedCollectionViewCell"
override func viewDidLoad() {
super.viewDidLoad()
getPosts()
view.addSubview(collectionView)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(NewsFeedCollectionViewCell.self, forCellWithReuseIdentifier: cellID)
collectionView.pin(to: view)
}
func getPosts() {
friends = FriendFactory().friends
allPosts = friends.flatMap{$0.posts}
var tempPosts = allPosts
for _ in allPosts {
if let index = tempPosts.indices.randomElement() {
let post = tempPosts[index]
shuffledPosts.append(post)
tempPosts.remove(at: index)
}
}
}
}
extension NewsFeedViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return shuffledPosts.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! NewsFeedCollectionViewCell
let post = shuffledPosts[indexPath.row]
let friend = friends.first(where: {$0.identifier == post?.authorID})
let text = post?.postText
cell.configurePostText(postText: text!)
return cell
}
}
NewsFeedCollectionViewCell:
import UIKit
class NewsFeedCollectionViewCell: UICollectionViewCell {
var selectedPost = Post()
var likeBarView: LikeBarView = {
let view = LikeBarView()
view.backgroundColor = .systemIndigo
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var postTextLabel: UILabel = {
let label = UILabel()
label.font = label.font.withSize(20)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.backgroundColor = .systemYellow
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addViews()
setupConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addViews() {
addSubview(likeBarView)
addSubview(postTextLabel)
}
func configurePostText(postText: String) {
postTextLabel.text = postText
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
layoutIfNeeded()
layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
return layoutAttributes
}
func setupConstraints() {
NSLayoutConstraint.activate([
likeBarView.topAnchor.constraint(equalTo: topAnchor, constant: 20),
likeBarView.leadingAnchor.constraint(equalTo: leadingAnchor),
likeBarView.trailingAnchor.constraint(equalTo: trailingAnchor),
likeBarView.heightAnchor.constraint(equalToConstant: 100),
postTextLabel.topAnchor.constraint(equalTo: likeBarView.bottomAnchor, constant: 20),
postTextLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
postTextLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
postTextLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20),
])
}
}
NewsFeedFlowLayout:
import UIKit
final class NewsFeedFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes]
layoutAttributesObjects?.forEach({ layoutAttributes in
if layoutAttributes.representedElementCategory == .cell {
if let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame {
layoutAttributes.frame = newFrame
}
}
})
return layoutAttributesObjects
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let collectionView = collectionView else {
fatalError()
}
guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
return nil
}
layoutAttributes.frame.origin.x = sectionInset.left
layoutAttributes.frame.size.width = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right
return layoutAttributes
}
}
I enclose the screenshot of the collection view I'm getting and the screenshot of the analysis of the error done here, which is saying that the height of the cell is not dynamic if I'm getting it right.
What am I doing wrong?
Adding this line of code to NewsFeedViewController silenced the error:
layout.estimatedItemSize = CGSize(width: 375, height: 200)

How can I match Custom View's frame with TableViewCell frame properly?

I have a TableView which contains image stretched into cell and I added a view for darkening the image. Since I want to draw this view only in the beginning and not every time I scroll in table I added my codes into awakeFromNib in TableViewCell.
TableViewCell.swift
#IBOutlet weak var equipmentImageView: UIImageView!
let darkFilter = UIView()
override func awakeFromNib() {
super.awakeFromNib()
darkFilter.backgroundColor = .black
darkFilter.layer.opacity = 0.6
darkFilter.frame = self.equipmentImageView.frame
equipmentImageView.addSubview(darkFilter)
}
My problem is that this view will not have the same width with the ImageView, so the effect I'm trying to implement takes half of the screen.
I followed this solution but the problem is that this solution will only apply after the cells recreate themselves by scrolling down and up again.
TableViewCell.swift
override func layoutSubviews() {
super.layoutSubviews()
darkFilter.backgroundColor = .black
darkFilter.layer.opacity = 0.6
darkFilter.frame = self.equipmentImageView.frame
equipmentImageView.addSubview(darkFilter)
}
How can I apply this solution before the cells are created and will not redraw when scrolling through it?
You could set the cell's .background color to .black, and then lower the alpha of the equipmentImageView and you'd achieve the same darkened effect without having to add a new UIView.
EDIT
To illustrate my point: here's a really sloppy and quick example I drew up in Playground with an image titled "Untitled.jpg" to prove fading the image with a black cell background works the same as adding a faded black layer on top of a cell with an image:
Picture of result:
code:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class CustomTableViewCell: UITableViewCell {
let myImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
if let sample = Bundle.main.path(forResource: "Untitled", ofType: "jpg") {
let image = UIImage(contentsOfFile: sample)
imageView.image = image
}
imageView.clipsToBounds = true
return imageView
}()
let blackView: UIView = {
let myView = UIView()
myView.translatesAutoresizingMaskIntoConstraints = false
myView.backgroundColor = .black
return myView
}()
let title: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .white
label.font = UIFont.systemFont(ofSize: 12)
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .black
let views = [myImageView,
blackView,
title]
views.forEach {
contentView.addSubview($0)
NSLayoutConstraint.activate([
$0.heightAnchor.constraint(equalTo: contentView.heightAnchor),
$0.widthAnchor.constraint(equalTo: contentView.widthAnchor),
$0.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
$0.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
])
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setCellViews(indexPath: IndexPath) {
blackView.alpha = 0
if indexPath.row % 2 == 0 {
if indexPath.row % 4 == 0 {
blackView.alpha = 0.4
title.text = "blackLayer added and faded"
} else {
backgroundColor = .black
myImageView.alpha = 0.6
title.text = "backView faded"
}
} else {
title.text = "not faded at all"
}
}
}
private let reuseId = "cellId"
class MyViewController : UIViewController {
let tableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.separatorStyle = .none
return tableView
}()
let data = ["one", "two", "three", "four", "five"]
override func viewDidLoad() {
super.viewDidLoad()
setTableView()
}
func setTableView() {
tableView.dataSource = self
tableView.delegate = self
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: reuseId)
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.heightAnchor.constraint(equalTo: view.heightAnchor),
tableView.widthAnchor.constraint(equalTo: view.widthAnchor),
tableView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
tableView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
}
}
extension MyViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseId, for: indexPath) as! CustomTableViewCell
cell.setCellViews(indexPath: indexPath)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
2 suggestions:
1) Since you are dealing with IBOutlets for the cell, you could also add the view on the IB on top of the image view, and then adding constrains to it to match up the image view size.
2) If you are aiming to adding it programmatically, you might need to setup its constraints based on the cell content view. For example:
darkFilter.backgroundColor = .black
darkFilter.layer.opacity = 0.6
contentView.addSubview(darkFilter)
darkFilter.translatesAutoresizingMaskIntoConstraints = false
darkFilter.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
darkFilter.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
darkFilter.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
darkFilter.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true

Updating auto layout after changing an object's frame size

I am creating a view using auto layout and the result is this.
After the view loads, then I grab some text data and fill the UITextView's. The "About me" item can be multiple lines, so I resize that specific frame. Then, I get the following.
You see how the about me textview is covering the next field? How can I resize the auto layout with the new about me textview size? I searched and found some suggestions to use setNeedsLayout and layoutIfNeeded, but none worked.
I am setting up the auto layout like the following:
inputsContainerView.addSubview(ageInput)
ageInput.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
ageInput.topAnchor.constraint(equalTo: inputsContainerView.topAnchor, constant: 10).isActive = true
ageInput.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
ageInput.heightAnchor.constraint(equalToConstant: 60).isActive = true
inputsContainerView.addSubview(genderInput)
genderInput.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
genderInput.topAnchor.constraint(equalTo: ageInput.bottomAnchor).isActive = true
genderInput.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
genderInput.heightAnchor.constraint(equalToConstant: 60).isActive = true
inputsContainerView.addSubview(aboutInput)
aboutInput.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
aboutInput.topAnchor.constraint(equalTo: genderInput.bottomAnchor).isActive = true
aboutInput.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
aboutInput.heightAnchor.constraint(equalToConstant: 60).isActive = true
inputsContainerView.addSubview(memberSinceInput)
memberSinceInput.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
memberSinceInput.topAnchor.constraint(equalTo: aboutInput.bottomAnchor).isActive = true
memberSinceInput.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
memberSinceInput.heightAnchor.constraint(equalToConstant: 60).isActive = true
After the view loads, I fetch data and resize the about me textview's frame using the following function:
func resizeTextView(_ textView: UITextView) {
let fixedWidth = textView.frame.size.width
textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
var newFrame = textView.frame
newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
textView.frame = newFrame
}
If I were you, I'd use UITableView to create this form and add the label and UITextView inside the cells.
You could do a UITableViewCell like below where I set the height of label to 60 and auto layout it. UITextView also using the auto layout and fitting the cell's bottom.
import UIKit
class UserDetailCell: UITableViewCell {
var userDetailLabel : UILabel = {
var label = UILabel()
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor.lightGray
return label
}()
var userDetailTextView : UITextView = {
var tv = UITextView()
tv.translatesAutoresizingMaskIntoConstraints = false
tv.isScrollEnabled = false
return tv
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupUI()
}
func setupUI(){
addSubview(userDetailLabel)
NSLayoutConstraint.activate([
userDetailLabel.topAnchor.constraint(equalTo: topAnchor),
userDetailLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 8),
userDetailLabel.rightAnchor.constraint(equalTo: rightAnchor),
userDetailLabel.heightAnchor.constraint(equalToConstant: 60)
])
addSubview(userDetailTextView)
NSLayoutConstraint.activate([
userDetailTextView.topAnchor.constraint(equalTo: userDetailLabel.bottomAnchor),
userDetailTextView.leftAnchor.constraint(equalTo: leftAnchor, constant: 8),
userDetailTextView.rightAnchor.constraint(equalTo: rightAnchor),
userDetailTextView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Then your UIViewController should be like below. I'm setting a delegate to UITextView inside the cellForRowAt indexPath method. Since I set the delegate, the textViewDidChange delegate method will be called. It is written inside an extension.
import UIKit
class UserDetailsController: UITableViewController {
let cellId = "cell"
var person = Person(myAge: 20, myGender: "Male", aboutMe: "Hello my name is jake waisee. What is your name? goayngeHello my name is jake waisee. What is your name? goayngeHello my name is jake waisee. What is your name? goayngeHello my name is jake waisee. What is your name? goaynge")
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UserDetailCell.self, forCellReuseIdentifier: cellId)
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 100
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserDetailCell
cell.userDetailTextView.delegate = self
cell.userDetailTextView.tag = indexPath.row
if indexPath.row == 0{
cell.userDetailLabel.text = "Age"
cell.userDetailTextView.text = "\(person.age)"
}else if indexPath.row == 1{
cell.userDetailLabel.text = "Gender"
cell.userDetailTextView.text = person.gender
}else if indexPath.row == 2{
cell.userDetailLabel.text = "About me"
cell.userDetailTextView.text = person.aboutMe
}
return cell
}
}
extension UserDetailsController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
print(textView.text)
if textView.tag == 2 {
person.aboutMe = textView.text
}else if textView.tag == 0 {
person.age = Int(textView.text) ?? 0
}else if textView.tag == 1 {
person.gender = textView.text
}
//this will keep the textview growing as we type
tableView.beginUpdates()
tableView.endUpdates()
}
}
Hope this helps you out. Your UI should look something like below.

Swift 3: Be able to tap view behind a collectionViewCell?

I am working on a new feature in my app where I want a user to have an image and then be able to swipe through a collectionView on top of that image, adding "filters". The issue that I have is that I want the user to still be able to interact with the image, but it is behind the collectionView. If I set the collectionViews inUsersInteractionEnabled to false, of course I then cannot use the collectionView at all. I am going for a similar functionality to Snapchats "scroll through filters" feature. Any help will be highly appreciated. Thank you guys. Code is below:
import UIKit
class FirstCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
func setupViews() {
backgroundColor = .clear
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class SecondCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
let cellView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.alpha = 0.25
return view
}()
func setupViews() {
backgroundColor = .clear
addSubview(cellView)
cellView.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class SecondController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .clear
cv.dataSource = self
cv.delegate = self
cv.translatesAutoresizingMaskIntoConstraints = false
cv.isPagingEnabled = true
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(FirstCell.self, forCellWithReuseIdentifier: "firstCell")
collectionView.register(SecondCell.self, forCellWithReuseIdentifier: "secondCell")
view.backgroundColor = .white
setupViews()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "firstCell", for: indexPath) as! FirstCell
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "secondCell", for: indexPath) as! SecondCell
return cell
}
}
lazy var imageView: UIImageView = {
let iv = UIImageView()
let image = UIImage(named: "img")
iv.image = image
iv.contentMode = .scaleAspectFill
iv.translatesAutoresizingMaskIntoConstraints = false
iv.isUserInteractionEnabled = true
iv.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap)))
return iv
}()
func handleTap() {
print(123)
}
func setupViews() {
view.addSubview(imageView)
view.addSubview(collectionView)
imageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
imageView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
imageView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
collectionView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
collectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
}
}
There is a hitTest(_::) method available to override in any of the UIView subclass, which you can implement in your custom UICollectionView subclass and forward taps from it to any other view (the image behind collection view in your case).
override func hitTest(_ point: CGPoint,
with event: UIEvent?) -> UIView? {
let hitView = super.hitTest(point, with: event)
if self.parentViewController.forwardTapToImageIfNeeded(point) {
return nil
}
return hitView
}
Where parentViewController is a reference to your SecondController instance, which should have forwardTapToImageIfNeeded(_) function to check if user's tap is on top of the image frame, and if it is — automatically pass tap event to it.
func forwardTapToImageIfNeeded(_ p: CGPoint) -> Bool {
/* Convert point to local coordinates */
let pointInSuperView = self.convert(p, from: collectionView)
/* Check if tap is on top of the image frame */
if imageBehindCollection.frame.contains(pointInSuperView) {
// Forward tap to the image `imageBehindCollection`
return true
}
return false
}
Note: I didn't test this particular code by myself, so it might need a few fixes in Xcode.

Layout constraints for custom UITtableViewCell not working

I'm creating custom UITableViewCells with a UILabel in each but can't get the label to display anywhere else than the top left corner of the cell, looks like this.
Constraints don't seem to be applied yet they're being called (reaching breakpoint). I tried to replace the UILabel by an UIImageView and apply the same constraints but nothing appears (i.e. table view cells are blank).
What am I missing?
View for cells:
import UIKit
class myTableViewCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
}
let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 16, weight: UIFontWeightLight)
label.text = "Sample"
label.backgroundColor = UIColor.red
return label
}()
func setupViews () {
addSubview(label)
//add constraints
let marginsGuide = self.contentView.layoutMarginsGuide
label.leadingAnchor.constraint(equalTo: marginsGuide.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: marginsGuide.trailingAnchor).isActive = true
label.topAnchor.constraint(equalTo: marginsGuide.topAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: marginsGuide.bottomAnchor).isActive = true
}
}
View controller:
import UIKit
class myViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var myTableView: UITableView = UITableView()
var myArray = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
//... do stuff incl. loading data into my myArray
let screenSize: CGRect = UIScreen.main.bounds
self.myTableView.frame = CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height)
self.myTableView.delegate = self
self.myTableView.dataSource = self
self.myTableView.register(IngredientListTableViewCell.self, forCellReuseIdentifier: "cell")
self.view.addSubview(myTableView)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.myTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! myTableViewCell
return cell
}
}
Change this
addSubview(label)
to this
contentView.addSubview(label)