ScrollView Doesn't Scroll in collectionViewCell - ios

I am creating a collage app. The functionality I am looking at in my collectionViewCell is exactly like this: How to make a UIImage Scrollable inside a UIScrollView?
What I have done till now:
Created a custom collectionViewCell embedded a UIScrollView in it and an imageView inside the scrollView. I have set their constraints. Following is my code for collectionViewCell :
override init(frame: CGRect) {
super.init(frame: frame)
// addSubview(scrollView)
insertSubview(scrollView, at: 0)
contentView.isUserInteractionEnabled = true
scrollView.contentMode = .scaleAspectFill
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(imageView)
scrollContentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.isScrollEnabled = true
imageView.isUserInteractionEnabled = false
scrollView.delegate = self
scrollView.maximumZoomScale = 5.0
scrollView.backgroundColor = .red
scrollViewDidScroll(scrollView)
// scrollViewWillBeginDragging(scrollView)
let constraints = [
// Scroll View Constraints
scrollView.topAnchor.constraint(equalTo: contentView.topAnchor,constant: 0.0)
scrollView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor,constant: 0.0),
scrollView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor,constant: 0.0),
scrollView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor,constant: 0.0),
// ImageView Constraints
imageView.topAnchor.constraint(equalTo: self.scrollView.topAnchor),
imageView.bottomAnchor.constraint(equalTo: self.scrollView.bottomAnchor),
imageView.trailingAnchor.constraint(equalTo: self.scrollView.trailingAnchor),
imageView.leadingAnchor.constraint(equalTo: self.scrollView.leadingAnchor)
]
NSLayoutConstraint.activate(constraints)
}
Another issue I face here is if I do insertSubview() instead of addSubview my collectionView function for didselectItemAt works fine else if I use addSubView the didselectItemFunction doesn't execute. Code For didSelectItem :
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if selectedImage[indexPath.row] != UIImage(named: "empty-image") {
collectionViewCellClass.scrollView.isUserInteractionEnabled = true
collectionViewCellClass.scrollViewDidScroll(collectionViewCellClass.scrollView)
collectionViewCellClass.scrollView.showsHorizontalScrollIndicator = true
}else {
collectionViewCellClass.imageView.isUserInteractionEnabled = false
self.currentIndex = indexPath.row
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary){
imagePicker.delegate = self
imagePicker.allowsEditing = false
imagePicker.sourceType = .savedPhotosAlbum
present(imagePicker, animated: true, completion: nil)
}
}
}
Code for CellForItemAt IndexPath :
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.imageView.image = selectedImage[indexPath.item]
// TODO: Round corners of the cell
cell.scrollView.frame = CGRect(x: 0, y: 0, width: cell.frame.size.width, height: cell.frame.size.height)
// cell.scrollView.contentSize = CGSize(width: cell.imageView.image!.size.width * 2, height: cell.imageView.image!.size.height * 2)
cell.scrollView.contentSize = cell.imageView.image?.size ?? .zero
cell.scrollView.contentMode = .center
cell.scrollView.isScrollEnabled = true
// cell.scrollContentView.frame = CGRect(x: 0, y: 0, width: cell.frame.size.width, height: cell.frame.size.height)
cell.imageView.frame = CGRect(x: 0, y: 0, width: cell.imageView.image!.size.width, height: cell.imageView.image!.size.height)
// cell.imageView.clipsToBounds = true
cell.backgroundColor = .black
return cell
}
I have tried changing the size of ImageView = cell.frame.size still no luck and changed the size for scrollView Frame as well to imageView.image.size but for some reason scrollView doesn't scroll.

Insertview vs addSubview
addSubview: add the view at the frontmost.
insertSubview: add the view at a specific index.
when you add your scrollview it using addSubView method it will block all the user interaction for the view's that are below it. That's why didSelect event is not triggered.
see this question
To make the scrollview scroll you need to set its contentSize which should be greater than scrollview's frame. try adding an image whose size is greater than collectionview cell.
scrollView.contentSize = imageView.bounds.size

Try adding the scrollView to the cells contentView:
self.contentView.addSubview(scrollView)

I finally found the Solution, following is what worked for me :
if selectedImage[indexPath.row] != UIImage(named: "empty-image"){
cell.contentView.isUserInteractionEnabled = true
}else{
cell.contentView.isUserInteractionEnabled = false
}
The UIImage(named: "empty-image") is the image that appears when a user doesn't have made any selection for his image. I wrote the above code in the cellForItemAt function. Also, I had to set imageView.frame equal to the original width and height of the image.
cell.imageView.frame = CGRect(x: 0, y: 0, width: cell.imageView.image!.size.width, height: cell.imageView.image!.size.height)
The above solution solved my problem. Hopefully, it will help someone.

Related

UIImageView not resizing as circle and UILabel not resizing within StackView and Custom Collection Cell

I am trying to resize my UIImageView as a circle, however; every time I try to resize the UIImageView, which is inside a StackView along with the UILabel, I keep on ending up with a more rectangular shape. Can someone show me where I am going wrong I have been stuck on this for days? Below is my code, and what it's trying to do is add the image with the label at the bottom, and the image is supposed to be round, and this is supposed to be for my collection view controller.
custom collection view cell
import UIKit
class CarerCollectionViewCell: UICollectionViewCell {
static let identifier = "CarerCollectionViewCell"
private let imageView: UIImageView = {
let imageView = UIImageView()
imageView.frame = CGRect(x: 0, y: 0, width: 20, height: 20);
//imageView.center = imageView.superview!.center;
imageView.contentMode = .scaleAspectFill
imageView.layer.borderWidth = 4
imageView.layer.masksToBounds = false
imageView.layer.borderColor = UIColor.orange.cgColor
imageView.layer.cornerRadius = imageView.frame.height / 2
return imageView
}()
private let carerNamelabel: UILabel = {
let carerNamelabel = UILabel()
carerNamelabel.layer.masksToBounds = false
carerNamelabel.font = .systemFont(ofSize: 12)
carerNamelabel.textAlignment = .center
carerNamelabel.layer.frame = CGRect(x: 0, y: 0, width: 50, height: 50);
return carerNamelabel
}()
private let stackView: UIStackView = {
let stackView = UIStackView()
stackView.layer.masksToBounds = false
stackView.axis = .vertical
stackView.alignment = .center
stackView.backgroundColor = .systemOrange
stackView.distribution = .fillProportionally
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
override init(frame: CGRect) {
super.init(frame: frame)
configureContentView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func configureContentView() {
imageView.clipsToBounds = true
stackView.clipsToBounds = true
carerNamelabel.clipsToBounds = true
contentView.addSubview(stackView)
configureStackView()
}
private func configureStackView() {
allContraints()
stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(carerNamelabel)
}
private func allContraints() {
setStackViewConstraint()
}
private func setStackViewConstraint() {
stackView.widthAnchor.constraint(equalTo: contentView.widthAnchor).isActive = true
stackView.heightAnchor.constraint(equalTo: contentView.heightAnchor).isActive = true
stackView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
}
public func configureImage(with imageName: String, andImageName labelName: String) {
imageView.image = UIImage(named: imageName)
carerNamelabel.text = labelName
}
override func layoutSubviews() {
super.layoutSubviews()
stackView.frame = contentView.bounds
}
override func prepareForReuse() {
super.prepareForReuse()
imageView.image = nil
carerNamelabel.text = nil
}
}
Below here is my code for the CollectionViewControler
custom collection view
import UIKit
class CarerViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.itemSize = CGSize(width: 120, height: 120)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(CarerCollectionViewCell.self, forCellWithReuseIdentifier: CarerCollectionViewCell.identifier)
collectionView.showsVerticalScrollIndicator = false
collectionView.backgroundColor = .clear
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
// Layout constraints for `collectionView`
NSLayoutConstraint.activate([
collectionView.widthAnchor.constraint(equalTo: view.widthAnchor),
collectionView.heightAnchor.constraint(lessThanOrEqualTo: view.heightAnchor, constant: 600),
collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 200),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
])
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CarerCollectionViewCell.identifier, for: indexPath) as! CarerCollectionViewCell
cell.configureImage(with: "m7opt04g_ms-dhoni-afp_625x300_06_July_20", andImageName: "IMAGE NO. 1")
return cell
}
}
Can somebody show or point to me what I am doing wrong, thank you
This is what I am trying to achieve
UIStackView could be tricky sometimes, you can embed your UIImageView in a UIView, and move your layout code to viewWillLayoutSubviews(), this also implys for the UILabel embed that also inside a UIView, so the containers UIViews will have a static frame for the UIStackViewto be layout correctly and whats inside them will only affect itself.

Change layout of UICollectionViewCell

I am using a UICollectionViewController to display a collection of UICollectionViewCell that a user can swipe between, on ios 13, swift 5, and UIKit. I ultimatelly want to re-draw the cell if the user has changed the orientation. In my controller, I have noticed that this piece of code gets executed on orientation change:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! MyCell
return cell
}
In MyCell, I am using this code to monitor then a layout is re-initialized:
override init(frame: CGRect) {
super.init(frame: frame)
setLayout()
print("### orientation:: \(UIDevice.current.orientation.isLandscape) ")
}
Result: This method is called 2 times. When the app starts, and when I change from portrait to landscape. Any subsequent changes, do not call it. I suspect that this is fine, as maybe both versions are cached ( a landscape and a portrait ). If I add code to the setLayout() method based on the UIDevice.current.orientation.isLandscape flag, nothing changes while it executes. Note: The actual result of UIDevice.current.orientation.isLandscape is correct.
Here is an example of what is in my setLayout() method:
let topImageViewContainer = UIView();
topImageViewContainer.translatesAutoresizingMaskIntoConstraints = false
topImageViewContainer.backgroundColor = .yellow
addSubview(topImageViewContainer)
NSLayoutConstraint.activate([
topImageViewContainer.topAnchor.constraint(equalTo: topAnchor),
topImageViewContainer.leftAnchor.constraint(equalTo: leftAnchor),
topImageViewContainer.rightAnchor.constraint(equalTo: rightAnchor),
topImageViewContainer.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.5)
])
if (UIDevice.current.orientation.isLandscape) {
let imageView = UIImageView(image: UIImage(named: "photo_a"))
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
topImageViewContainer.addSubview(imageView)
} else {
let imageView = UIImageView(image: UIImage(named: "photo_b"))
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
topImageViewContainer.addSubview(imageView)
}
In the example above, the code with "photo_a" is executing, but the ui is still showing photo_b. I suspect I am doing something wrong, and I need to somehow migrate towards an "Adaptive layout" as mentioned here: Different layouts in portrait and landscape mode . However, I am not sure how I could hook in some "viewWillTransition*" functions into a UICollectionViewCell that has a UIView.
How can I adjust my MyCell class, that implements UICollectionViewCell, to allow the cell to be updated when the user changes from landscape to protrait in a Swift intended way?
Hi try to check if is landscape or portrait in cellForItem, my example:
set colloectionView, flowLayaout and cell id:
class ViewController: UIViewController {
private var collectionView: UICollectionView?
let layout = UICollectionViewFlowLayout()
let cellId = "cellId"
now in viewDidLoad set collection view and layout parameters and the collection constraints:
layout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView?.delegate = self
collectionView?.dataSource = self
collectionView?.backgroundColor = .clear
collectionView?.register(BottomCellTableViewCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.contentInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
collectionView?.showsHorizontalScrollIndicator = false
collectionView?.translatesAutoresizingMaskIntoConstraints = false
guard let collection = collectionView else { return }
view.addSubview(collection)
layout.itemSize = CGSize(width: view.bounds.size.width / 2, height: 200)
collection.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20).isActive = true
collection.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
collection.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
collection.heightAnchor.constraint(equalToConstant: 200).isActive = true
after that conform to delegate and datasource in a separate extension (more clean code) and set in it numberOfItems and cellForRow:
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! BottomCellTableViewCell
// check here device orientation
if UIDevice.current.orientation.isLandscape {
let image = UIImage(named: "meB")
cell.imageV.image = image
} else {
let image = UIImage(named: "me")
cell.imageV.image = image
}
// reload the view and the collection
DispatchQueue.main.async {
self.collectionView?.reloadData()
}
return cell
}
}
In collectionViewCell adding a subview directly to the cell, and pinning the image view to the cell, is wrong. You add it to a contentView and pin to it relative constraints:
class BottomCellTableViewCell: UICollectionViewCell {
let imageV: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "me")
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .red
contentView.addSubview(imageV)
imageV.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
imageV.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
imageV.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
imageV.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is the result:

Why UICollectionView is not responding at all?

I'm setting up a UICollectionView inside a ViewController. However, it won't respond to any user interaction, didSelectItemAt function is not getting called and I am unable to scroll it.
I have set the DataSource and Delegate properly inside the viewDidLoad(). Here is my code:
class WelcomeController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
let padding: CGFloat = 30
var sources = [Source]()
let sourceCellId = "sourceCellId"
func fetchSources() {
ApiSourceService.sharedInstance.fetchSources() { (root: Sources) in
self.sources = root.source
self.sourceCollectionView.reloadData()
}
}
let backgroundImage: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "background")
iv.contentMode = .scaleAspectFill
iv.translatesAutoresizingMaskIntoConstraints = false
iv.clipsToBounds = false
return iv
}()
let overlayView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
return view
}()
let logo: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "mylogo")
iv.translatesAutoresizingMaskIntoConstraints = false
iv.clipsToBounds = false
return iv
}()
let defaultButton: UIButton = {
let ub = UIButton()
ub.translatesAutoresizingMaskIntoConstraints = false
ub.setTitle("Inloggen met E-mail", for: .normal)
ub.setImage(UIImage(named: "envelope"), for: .normal)
ub.imageEdgeInsets = UIEdgeInsetsMake(15, 0, 15, 0)
ub.imageView?.contentMode = .scaleAspectFit
ub.contentHorizontalAlignment = .left
ub.titleLabel?.font = UIFont.init(name: "Raleway-Regular", size: 16)
ub.backgroundColor = .cuOrange
return ub
}()
let continueWithoutButton: UIButton = {
let ub = UIButton()
ub.translatesAutoresizingMaskIntoConstraints = false
let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: "Doorgaan zonder in te loggen")
let textRange = NSMakeRange(0, attributedString.length)
attributedString.setColor(color: .cuOrange, forText: "Doorgaan")
ub.setAttributedTitle(attributedString, for: .normal)
ub.contentHorizontalAlignment = .center
ub.titleLabel?.font = UIFont.init(name: "Raleway-Regular", size: 16)
ub.titleLabel?.textColor = .white
return ub
}()
let sourceCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.backgroundColor = .white
return cv
}()
let termsButton: UIButton = {
let ub = UIButton()
ub.translatesAutoresizingMaskIntoConstraints = false
let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: "Met het maken van een account \nof bij inloggen, ga ik akkoord \nmet servicevoorwaarden.")
let textRange = NSMakeRange(0, attributedString.length)
attributedString.setColor(color: .cuOrange, forText: "servicevoorwaarden")
ub.setAttributedTitle(attributedString, for: .normal)
ub.contentHorizontalAlignment = .center
ub.contentVerticalAlignment = .bottom
ub.titleLabel?.lineBreakMode = .byWordWrapping
ub.titleLabel?.font = UIFont.init(name: "Raleway-Regular", size: 14)
ub.titleLabel?.textColor = .white
ub.titleLabel?.textAlignment = .center
return ub
}()
override func viewDidLoad() {
super.viewDidLoad()
fetchSources()
sourceCollectionView.dataSource = self
sourceCollectionView.delegate = self
sourceCollectionView.register(SourceCell.self, forCellWithReuseIdentifier: sourceCellId)
view.addSubview(backgroundImage)
view.addSubview(overlayView)
backgroundImage.addSubview(termsButton)
backgroundImage.addSubview(logo)
backgroundImage.addSubview(sourceCollectionView)
backgroundImage.widthAnchor.constraint(lessThanOrEqualToConstant: 375).isActive = true
backgroundImage.heightAnchor.constraint(lessThanOrEqualToConstant: 667).isActive = true
backgroundImage.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
backgroundImage.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
logo.centerXAnchor.constraint(equalTo: backgroundImage.centerXAnchor).isActive = true
logo.topAnchor.constraint(equalTo: backgroundImage.topAnchor, constant: padding).isActive = true
logo.widthAnchor.constraint(equalToConstant: 107).isActive = true
logo.heightAnchor.constraint(equalToConstant: 100).isActive = true
sourceCollectionView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
sourceCollectionView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -(padding*1.5)).isActive = true
sourceCollectionView.topAnchor.constraint(equalTo: logo.bottomAnchor, constant: padding).isActive = true
sourceCollectionView.bottomAnchor.constraint(equalTo: termsButton.topAnchor, constant: -(padding*2)).isActive = true
termsButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -padding).isActive = true
termsButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
termsButton.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -(padding*2)).isActive = true
termsButton.heightAnchor.constraint(equalToConstant: 40).isActive = true
sourceCollectionView.reloadData()
}
#objc func closeWelcomeController() {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.switchViewControllers()
self.dismiss(animated: true, completion: {
})
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = self.sourceCollectionView.frame.width
let height = CGFloat(50)
return CGSize(width: width, height: height)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return sources.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print(sources[indexPath.item].feed_name)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: sourceCellId, for: indexPath) as! SourceCell
cell.preservesSuperviewLayoutMargins = true
cell.source = sources[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("selecting")
}}
Anyone knows what I am doing wrong here?
I hope there isn't just a "dumb" error inside the code why it's not working, but it's been eating my brain for the past few hours.
It seems that it is inappropriate to add sourceCollectionView as a subview in backgroundImage which is UIImageView! Logically, image view doesn't represent a container view (such as UIView UIStackView, UIScrollView) i.e even if the code lets you do that, it still non-sensible; Furthermore, even if adding a subview to an image view is ok (which is not), image views by default are user interaction disabled (userInteractionEnabled is false by default for image views), that's why you are unable to even scroll the collection view, it is a part (subview) of a disabled user interaction component.
In your viewDidLoad(), you are implementing:
backgroundImage.addSubview(termsButton)
backgroundImage.addSubview(logo)
backgroundImage.addSubview(sourceCollectionView)
Don't do this, instead, add them to main view of the view controller:
view.addSubview(termsButton)
view.addSubview(logo)
view.addSubview(sourceCollectionView)
For the purpose of checking if its the reason of the issue, at least do it for the collection view (view.addSubview(sourceCollectionView)).
And for the purpose of organizing the hierarchy of the view (which on is on top of the other), you could use both: bringSubview(toFront:) or sendSubview(toBack:). For instance, after adding the collection view into the view, you might need to:
view.bringSubview(toFront: sourceCollectionView)
to make sure that the collection view is on top of all components.
I see you add sourceCollectionView to subview of backgroundImage. Your backgroundImage is UIImageView so UIImageView doesn't have user interaction except you need to enable it. The problem of your code is
backgroundImage.addSubview(sourceCollectionView)
For my suggestion you should create UIView or other views that can interact with user interaction. It will work fine.

Add subview on tableView cells causes a sad patchwork

I want to give a chat aspect to a table view of messages in my iPhone app.
In order to perform a quality render I add two subviews: the text and the "container" which is just a view with background color.
Even if it works the first time, when I scroll, it becomes really messy because it keeps adding lots of subviews.
Here you can see it when clean
And then when it has become messy
Here is the function to handle the transform, it's called when scrolling.
func configChatCell(cell: UITableViewCell, text: String, color:UIColor)
{
cell.textLabel?.numberOfLines = 0
cell.textLabel?.lineBreakMode = NSLineBreakMode.ByWordWrapping
cell.textLabel?.textColor = UIColor.whiteColor()
let fixedWidth = cell.bounds.width - 150
let textView: UITextView = UITextView(frame: CGRect(x: 0, y: 0, width: 10, height: CGFloat.max))
textView.text = text
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max))
var newFrame = textView.frame
newFrame.size = CGSize(width: min(newSize.width, fixedWidth), height: newSize.height)
textView.sizeThatFits(newFrame.size)
textView.frame = newFrame
textView.backgroundColor = UIColor.clearColor()
self.rowHeight = textView.frame.height+20
let view = UIView()
view.backgroundColor = color
print(textView.frame.height+10)
view.frame = CGRect(x: 0, y: 5, width: textView.frame.width+50, height: textView.frame.height+10)
view.layer.cornerRadius = 5
cell.backgroundColor = UIColor.clearColor()
cell.contentView.addSubview(view)
cell.contentView.addSubview(textView)
cell.contentView.sendSubviewToBack(view)
}
If I remove the subviews each time I scroll, nothing appears on screen.
Can somebody help me to find a solution? Or is there any other way to do this?
Thanks in advance!
I quickly wrote up something for this.
It starts with the ChatCell
class ChatCell: UITableViewCell {
var messageLabel: UILabel? {
didSet {
messageLabel?.text = message
}
}
var message: String? {
didSet {
messageLabel?.text = message
}
}
class func messageCell(withText text: String, leading: Bool = true) -> ChatCell {
let cell = ChatCell()
cell.message = text
// Make the container
let container = UIView()
container.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(container)
container.topAnchor.constraintEqualToAnchor(cell.contentView.topAnchor, constant: 8).active = true
container.bottomAnchor.constraintEqualToAnchor(cell.contentView.bottomAnchor, constant: -8).active = true
if leading {
container.leadingAnchor.constraintEqualToAnchor(cell.contentView.leadingAnchor, constant: leading ? 8 : 8*8).active = true
container.trailingAnchor.constraintLessThanOrEqualToAnchor(cell.contentView.trailingAnchor, constant: leading ? -8*8 : -8).active = true
} else {
container.leadingAnchor.constraintGreaterThanOrEqualToAnchor(cell.contentView.leadingAnchor, constant: leading ? 8 : 8*8).active = true
container.trailingAnchor.constraintEqualToAnchor(cell.contentView.trailingAnchor, constant: leading ? -8*8 : -8).active = true
}
// Make the messageLabel.
let messageLabel = UILabel()
messageLabel.numberOfLines = 0
messageLabel.textColor = UIColor.whiteColor()
messageLabel.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(messageLabel)
// Add constraints.
messageLabel.topAnchor.constraintEqualToAnchor(container.topAnchor, constant: 8).active = true
messageLabel.bottomAnchor.constraintEqualToAnchor(container.bottomAnchor, constant: -8).active = true
messageLabel.leadingAnchor.constraintEqualToAnchor(container.leadingAnchor, constant: 8).active = true
messageLabel.trailingAnchor.constraintEqualToAnchor(container.trailingAnchor, constant: -8).active = true
cell.messageLabel = messageLabel
container.backgroundColor = UIColor(red:0.19, green:0.70, blue:1.00, alpha:1.00)
container.layer.cornerRadius = 12.0
return cell
}
}
The cell also includes support for leading and trailing messages, for back and forth conversation. Perhaps make an array of tuples like this:
let messages: [(message: String, leading: Bool)] = [("Hello", true), ("My name is John Doe and this works quite well", false), ("I would agree", true)]
Then in your tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell you could do this:
let cell = ChatCell.messageCell(withText: messages[indexPath.row].message, leading: messages[indexPath.row].leading)
return cell
Let me know if this works for you. I tested it in a Playground and it works as expected
Assuming that your configureChatCell is called from tableView:cellForRowAtIndexPath:, then #Paulw11 is right; cells are reused, so you should only make changes that are unique to that row in the table. In your example, the only calls that you should be making in your method are textView.text = text and the ones to resize the textView to fit. Everything else should go in a dynamic cell prototype in the storyboard or, if you want to do everything in code (which I have a bad feeling you do), then put the rest of the configuration in a UITableViewCell subclass, then register that subclass with your table view.
I write something like this. It's simple but can elucidate solution
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseid", forIndexPath: indexPath)
// Configure the cell...
let contentView = cell.contentView
let subviews = contentView.subviews
for view in subviews {
if 100 == view.tag {
let label = view as! UILabel
label.text = self.datas[indexPath.row]
} else if 200 == view.tag {
view.layer.cornerRadius = 10.0
}
}
return cell
}
the key point is config every thing in tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
the view in code with tag 200 is a background view has same frame with label, I use layout constraint in storyboard to make sure its size and position.

How to set custom margins for a UITableViewCell's .selectedBackgroundView

I'm using a table view to display a tree structure. Each cell corresponds to a node that the user can expand or collapse. The level of each node is visualized by having increasingly large indents at the leading edge of the cells. Those indents are set by using layoutMargins. This seems to work well for the cell's label and separators. Here's some code:
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
let cellLevel = cellLevelForIndexPath(indexPath)
let insets: UIEdgeInsets = UIEdgeInsetsMake(0.0, CGFloat(cellLevel) * 20.0, 0.0, 0.0)
cell.separatorInset = insets
cell.layoutMargins = insets
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cellId") as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cellId")
}
cell!.preservesSuperviewLayoutMargins = false
cell!.backgroundColor = UIColor.clearColor()
let cellLevel = cellLevelForIndexPath(indexPath)
if let textlabel = cell!.textLabel {
textlabel.text = "Cell # level \(cellLevel)"
textlabel.textColor = UIColor.blackColor()
}
cell!.selectedBackgroundView = UIView(frame: CGRectZero)
cell!.selectedBackgroundView.backgroundColor = UIColor.cyanColor()
return cell!
}
The resulting table looks like this:
The question I'm facing now is this: how can I elegantly apply the same indent to the cell's .selectedBackgroundView, so that it appears flush with the text and separator line? The end result should look something like this:
Note: I'm currently achieving the desired effect by making the .selectedBackgroundView more complex and adding background-colored subviews of varying size that effectively mask parts of the cell, e.g. like this:
let maskView = UIView(frame: CGRectMake(0.0, 0.0, CGFloat(cellLevel) * 20.0, cell!.bounds.height))
maskView.backgroundColor = tableView.backgroundColor
cell!.selectedBackgroundView.addSubview(maskView)
But I strongly feel that there must be a nicer way to do this.
Figured out a way to make it work. The trick for me was to stop thinking about the .selectedBackgroundView as the visible highlight itself (and thus trying to mask or resize it) and to treat it more like a canvas instead.
Here's what I ended up doing. First a more convenient way to get the appropriate inset for each level:
let tableLevelBaseInset = UIEdgeInsetsMake(0.0, 20.0, 0.0, 0.0)
private func cellIndentForLevel(cellLevel: Int) -> CGFloat {
return CGFloat(cellLevel) * tableLevelBaseInset.left
}
And then in the cellForRowAtIndexPath:
cell!.selectedBackgroundView = UIView(frame: CGRectZero)
cell!.selectedBackgroundView.backgroundColor = UIColor.clearColor()
let highlight = UIView(frame: CGRectOffset(cell!.bounds, cellIndentForLevel(cellLevel), 0.0))
highlight.backgroundColor = UIColor.cyanColor()
cell!.selectedBackgroundView.addSubview(highlight)
Seems to work nicely.
var selectedView = UIView()
override func awakeFromNib() {
super.awakeFromNib()
self.selectedBackgroundView = {
let view = UIView()
view.backgroundColor = UIColor.clear
selectedView.translatesAutoresizingMaskIntoConstraints = false
selectedView.backgroundColor = .hetro_systemGray6
selectedView.roundAllCorners(radius: 8)
view.addSubview(selectedView)
selectedView.topAnchor.constraint(equalTo: view.topAnchor, constant: 5).isActive = true
selectedView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -5).isActive = true
selectedView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
selectedView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
return view
}()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
selectedBackgroundView?.isHidden = false
} else {
selectedBackgroundView?.isHidden = true
}
}

Resources