images in UICollectionViewCell of UICollectionView created programatically overlap after scrolling - ios

enter code hereI tried creating a UICollectionView programatically in swift, Then added some images to the UICollectionViewCell (s). The display works fine but when i scroll down the view and come back up again the cells start showing images superimposed on one another(the newer ones overlap on the older ones). I read that recycling of the views happen which causes this problem. I tried a lot but could not find a solution.
I request you all to please help me find a solution
thanks
(this code can be run by adding some images with the names)
//
// ViewController1.swift
// Gridtest
import UIKit
class ViewController1: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var collectionView: UICollectionView!
var tapStatusHolderArray: Array<Bool> = [Bool]()
var cellCaptureArray: Array<UICollectionViewCell> = [UICollectionViewCell]()
var accountsIdHohderArray: Array<Int16> = [Int16]()
var accountsNameHolderArray: Array<String> = [String]()
var providerNameHolderArray: Array<String> = [String]()
var accountStatusHolderArray: Array<String> = [String]()
var accountsImageNameHolder: Array<String> = [String]()
override func viewDidLoad() {
super.viewDidLoad()
//data loader open
tapStatusHolderArray.append(false)
accountsIdHohderArray.append(100)
accountsNameHolderArray.append("account 1")
providerNameHolderArray.append("twitter")
accountStatusHolderArray.append("enabled")
accountsImageNameHolder.append("default_user_image")
tapStatusHolderArray.append(false)
accountsIdHohderArray.append(101)
accountsNameHolderArray.append("account 2")
providerNameHolderArray.append("facebook")
accountStatusHolderArray.append("enabled")
accountsImageNameHolder.append("default_user_image")
tapStatusHolderArray.append(false)
accountsIdHohderArray.append(102)
accountsNameHolderArray.append("account 3")
providerNameHolderArray.append("linkedin")
accountStatusHolderArray.append("enabled")
accountsImageNameHolder.append("default_user_image")
tapStatusHolderArray.append(false)
accountsIdHohderArray.append(100)
accountsNameHolderArray.append("account 1")
providerNameHolderArray.append("twitter")
accountStatusHolderArray.append("enabled")
accountsImageNameHolder.append("default_user_image")
tapStatusHolderArray.append(false)
accountsIdHohderArray.append(101)
accountsNameHolderArray.append("account 2")
providerNameHolderArray.append("facebook")
accountStatusHolderArray.append("enabled")
accountsImageNameHolder.append("default_user_image")
tapStatusHolderArray.append(false)
accountsIdHohderArray.append(102)
accountsNameHolderArray.append("account 3")
providerNameHolderArray.append("linkedin")
accountStatusHolderArray.append("enabled")
accountsImageNameHolder.append("default_user_image")
tapStatusHolderArray.append(false)
accountsIdHohderArray.append(100)
accountsNameHolderArray.append("account 1")
providerNameHolderArray.append("twitter")
accountStatusHolderArray.append("enabled")
accountsImageNameHolder.append("default_user_image")
tapStatusHolderArray.append(false)
accountsIdHohderArray.append(101)
accountsNameHolderArray.append("account 2")
providerNameHolderArray.append("facebook")
accountStatusHolderArray.append("enabled")
accountsImageNameHolder.append("default_user_image")
tapStatusHolderArray.append(false)
accountsIdHohderArray.append(102)
accountsNameHolderArray.append("account 3")
providerNameHolderArray.append("linkedin")
accountStatusHolderArray.append("enabled")
accountsImageNameHolder.append("default_user_image")
//data loader close
// Do any additional setup after loading the view.
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: 70, height: 70)
collectionView = UICollectionView(frame: CGRect(x: self.view.frame.origin.x, y: self.view.frame.origin.y, width: 200, height: 200), collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
collectionView.backgroundColor = UIColor.whiteColor()
self.view.addSubview(collectionView)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.tapStatusHolderArray.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: UICollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! UICollectionViewCell
cell.backgroundColor = UIColor.yellowColor()
//create views here
let accountsImageView = UIImageView()
accountsImageView.setTranslatesAutoresizingMaskIntoConstraints(false)
accountsImageView.image = UIImage(named: "default_user_image")
//tap listener for the accounts image view
let tapAccountsImageView = UITapGestureRecognizer(target: self, action: Selector("tapGesture:"))
accountsImageView.tag = indexPath.row
accountsImageView.addGestureRecognizer(tapAccountsImageView)
accountsImageView.userInteractionEnabled = true
let providerImageView = UIImageView()
providerImageView.setTranslatesAutoresizingMaskIntoConstraints(false)
providerImageView.image = UIImage(named: translateProviderNameToImageName(providerNameHolderArray[indexPath.row]))
let checkboxImageView = UIImageView()
checkboxImageView.setTranslatesAutoresizingMaskIntoConstraints(false)
checkboxImageView.image = UIImage(named: "checkbox_unchecked")
//tap listener for the checkbox image view
let tapCheckbox = UITapGestureRecognizer(target: self, action: Selector("tapGesture:"))
checkboxImageView.tag = indexPath.row
checkboxImageView.addGestureRecognizer(tapCheckbox)
checkboxImageView.userInteractionEnabled = true
/************************************************************/
//add subviews here
cell.addSubview(accountsImageView)
cell.addSubview(providerImageView)
cell.addSubview(checkboxImageView)
/************************************************************/
//view dictionary
let viewsDictionary = [
"accountsImageView":accountsImageView,
"providerImageView":providerImageView,
"checkboxImageView":checkboxImageView
]
/************************************************************/
//apply size constraints
//accountsImageView
//1
let accountsImageView_constraint_H = NSLayoutConstraint.constraintsWithVisualFormat(
"H:[accountsImageView(50)]",
options: NSLayoutFormatOptions(rawValue:0),
metrics: nil,
views: viewsDictionary)
let accountsImageView_constraint_V = NSLayoutConstraint.constraintsWithVisualFormat(
"V:[accountsImageView(50)]",
options: NSLayoutFormatOptions(rawValue:0),
metrics: nil, views: viewsDictionary)
accountsImageView.addConstraints(accountsImageView_constraint_H)
accountsImageView.addConstraints(accountsImageView_constraint_V)
//2
let providerImageView_constraint_H = NSLayoutConstraint.constraintsWithVisualFormat(
"H:[providerImageView(20)]",
options: NSLayoutFormatOptions(rawValue:0),
metrics: nil,
views: viewsDictionary)
let providerImageView_constraint_V = NSLayoutConstraint.constraintsWithVisualFormat(
"V:[providerImageView(20)]",
options: NSLayoutFormatOptions(rawValue:0),
metrics: nil, views: viewsDictionary)
providerImageView.addConstraints(providerImageView_constraint_H)
providerImageView.addConstraints(providerImageView_constraint_V)
//3
let checkboxImageView_constraint_H = NSLayoutConstraint.constraintsWithVisualFormat(
"H:[checkboxImageView(20)]",
options: NSLayoutFormatOptions(rawValue:0),
metrics: nil,
views: viewsDictionary)
let checkboxImageView_constraint_V = NSLayoutConstraint.constraintsWithVisualFormat(
"V:[checkboxImageView(20)]",
options: NSLayoutFormatOptions(rawValue:0),
metrics: nil, views: viewsDictionary)
checkboxImageView.addConstraints(checkboxImageView_constraint_H)
checkboxImageView.addConstraints(checkboxImageView_constraint_V)
/**************************************************************/
//position constraints
//views
//1
let view_constraint_H = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-[accountsImageView]",
options: NSLayoutFormatOptions(rawValue:0),
metrics: nil, views: viewsDictionary)
let view_constraint_V = NSLayoutConstraint.constraintsWithVisualFormat(
"V:|-[accountsImageView]",
options: NSLayoutFormatOptions.AlignAllLeading,
metrics: nil, views: viewsDictionary)
cell.addConstraints(view_constraint_H)
cell.addConstraints(view_constraint_V)
//2
let view_constraint_H1 = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-45-[providerImageView]",
options: NSLayoutFormatOptions(rawValue:0),
metrics: nil, views: viewsDictionary)
let view_constraint_V1 = NSLayoutConstraint.constraintsWithVisualFormat(
"V:[providerImageView]-50-|",
options: NSLayoutFormatOptions.AlignAllLeading,
metrics: nil, views: viewsDictionary)
cell.addConstraints(view_constraint_H1)
cell.addConstraints(view_constraint_V1)
//3
let view_constraint_H2 = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-0-[checkboxImageView]",
options: NSLayoutFormatOptions(rawValue:0),
metrics: nil, views: viewsDictionary)
let view_constraint_V2 = NSLayoutConstraint.constraintsWithVisualFormat(
"V:[checkboxImageView]-5-|",
options: NSLayoutFormatOptions.AlignAllLeading,
metrics: nil, views: viewsDictionary)
cell.addConstraints(view_constraint_H2)
cell.addConstraints(view_constraint_V2)
/***************************************************************/
//set initial status for the cell checkbox
if(tapStatusHolderArray[indexPath.row] == true){
//set the checkbox as checked
checkboxImageView.image = UIImage(named: "checked_checkbox")
}else{
//set checkbox as unchecked
checkboxImageView.image = UIImage(named: "checkbox_unchecked")
}
//load the cell into the array
cellCaptureArray.append(cell)
return cell
}
func tapGesture(sender: UITapGestureRecognizer)
{
/*var index: Int = sender.view!.tag
if(checkedStatusHolderArray[index] == false){
checkedStatusHolderArray[index] = true
(sender.view as! UIImageView).image = UIImage(named: "checked_checkbox")
}else{
checkedStatusHolderArray[index] = false
(sender.view as! UIImageView).image = UIImage(named: "checkbox_unchecked")
}*///old code
/*
this code extracts the index from the tag attached to the sender
In this case the sender could be either the checkboxIV or the accountIV
both have the same tag. The tag is the indexpath.row for the corresponding cell.
The indexpath.row is used to signal the tap gesture recognizer about the view being tapped.
The tap gesture recognizer is asigned to both the views checkboxIV and the accountIV
so that the user can tap on either to allow check and uncheck the checkbox. Both the tap
gestures reconizers do the same function.
The cell inflated are saved inside the checkedStatusHolderArray so that using the index
extracted above we can change the status of the checkboxIV of the corresponding cell.
*/
var index: Int = sender.view!.tag
var cell: UICollectionViewCell = cellCaptureArray[index]
if(tapStatusHolderArray[index] == false){
tapStatusHolderArray[index] = true
let subViews = cell.subviews
//assuming that the order in which the views were added will be maintained
let checkboxIV = subViews[3] as! UIImageView
checkboxIV.image = UIImage(named: "checked_checkbox")
}else{
tapStatusHolderArray[index] = false
let subViews = cell.subviews
//assuming that the order in which the views were added will be maintained
let checkboxIV = subViews[3] as! UIImageView
checkboxIV.image = UIImage(named: "checkbox_unchecked")
}
/*the reson for choosing [3] for the subvies is that at
index 0 the superview is present
index 1 the accountsImageView is present
index 2 the providerImageView is present
index 3 the checkboxImageView is present*/
}
func translateProviderNameToImageName(var provider: String) -> String{
switch provider{
case "twitter":
return "twitter_transparent"
case "facebook":
return "facebook_transparent"
case "linkedin":
return "linkedin-follow"
default:
return "twitter_transparent"
}
}
}

first reset imageview and then try to load
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
cell.imageView.image = nil;
cell.imageView.image = [UIImage imagewithNamed:#"imagename"];

Related

Trying to create effect where a collection view slides over another collection view on scroll

I have two collection views, layered one on top of the other in my ViewController. The collection view on top is a horizontal scrolling collection view, while the one underneath scrolls vertically. I'm trying to create an effect where scrolling the lower collection view will cause it to move upwards to cover the upper collection view, like a sort of parallax effect.
I managed to get the effect by pinning both views to the top of the screen and modifying the lower collection view's content inset when scrolling, but this approach means that I cannot interact with the upper collection view, even when it's exposed. I'm looking for either a modification to this method, or a new method, that would allow me to retain this effect as well as interact with the upper collection view. Any suggestions on how to approach this problem? Thanks.
The code I have at the moment (more or less):
func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollView.contentInset.top = max(0, min(tendToSoon.frame.height, -scrollView.contentOffset.y))
}
Gif example
Unscrolled:
Slightly scrolled:
More scrolled:
Add two collectionviews in the view controller and set contentInset.top to height of the first colletionview. And contentOffset.y to minus value of contentInset.top. And override point(inside:with:) and return true only if y value is greater than 0.
Make sure the second collectionview's background color is clear.
class ViewController: UIViewController {
let topCollectionTitle = UILabel()
let topCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewLayout())
let bottomCollectionView = BottomCollectionView(frame: .zero, collectionViewLayout: UICollectionViewLayout())
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
topCollectionTitle.textAlignment = .center
topCollectionTitle.text = "Top Collectionview"
topCollectionTitle.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(topCollectionTitle)
let topLayout = UICollectionViewFlowLayout()
topLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
topLayout.itemSize = CGSize(width: self.view.bounds.width / 5, height: 150)
topLayout.scrollDirection = .horizontal
topLayout.minimumLineSpacing = 0
topLayout.minimumInteritemSpacing = 0
topCollectionView.contentInsetAdjustmentBehavior = .never
topCollectionView.collectionViewLayout = topLayout
topCollectionView.backgroundColor = .white
topCollectionView.register(CellHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "CellHeader")
topCollectionView.register(ImgCell.self, forCellWithReuseIdentifier: "ImgCell")
topCollectionView.delegate = self
topCollectionView.dataSource = self
topCollectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(topCollectionView)
let bottomLayout = UICollectionViewFlowLayout()
bottomLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
bottomLayout.itemSize = CGSize(width: self.view.bounds.width / 2, height: 150)
bottomLayout.scrollDirection = .vertical
bottomLayout.minimumLineSpacing = 0
bottomLayout.minimumInteritemSpacing = 0
bottomLayout.headerReferenceSize = CGSize(width: 50, height: 50)
bottomCollectionView.collectionViewLayout = bottomLayout
bottomCollectionView.backgroundColor = .clear
bottomCollectionView.register(CellHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "CellHeader")
bottomCollectionView.register(ImgCell.self, forCellWithReuseIdentifier: "ImgCell")
bottomCollectionView.delegate = self
bottomCollectionView.dataSource = self
bottomCollectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(bottomCollectionView)
bottomCollectionView.contentInset.top = 200
bottomCollectionView.contentOffset.y = -200
topCollectionTitle.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[topCollectionView]|", options: [], metrics: nil, views: ["topCollectionView":topCollectionView]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[topCollectionTitle(30)][topCollectionView(200)]", options: [], metrics: nil, views: ["topCollectionView":topCollectionView,"topCollectionTitle":topCollectionTitle]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-(10)-[topCollectionTitle]|", options: [], metrics: nil, views: ["topCollectionTitle":topCollectionTitle]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[bottomCollectionView]|", options: [], metrics: nil, views: ["bottomCollectionView":bottomCollectionView]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[bottomCollectionView]|", options: [], metrics: nil, views: ["bottomCollectionView":bottomCollectionView]))
}
}
extension ViewController: UICollectionViewDelegate {
}
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 25
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImgCell", for: indexPath) as? ImgCell ?? ImgCell()
cell.imgView.backgroundColor = .red
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionView.elementKindSectionHeader {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "CellHeader", for: indexPath) as? CellHeader
if collectionView == bottomCollectionView {
header?.titleLbl.text = "Bottom CollectionView"
}
return header!
}
return UICollectionReusableView()
}
}
class ImgCell: UICollectionViewCell {
let imgView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupViews()
}
func setupViews() {
backgroundColor = .white
imgView.layer.cornerRadius = 5.0
imgView.layer.masksToBounds = true
imgView.translatesAutoresizingMaskIntoConstraints = false
addSubview(imgView)
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-(5)-[imgView]-(5)-|", options: [], metrics: nil, views: ["imgView":imgView]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-(5)-[imgView]-(5)-|", options: [], metrics: nil, views: ["imgView":imgView]))
}
}
class CellHeader: UICollectionReusableView {
let titleLbl = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupViews()
}
func setupViews() {
backgroundColor = .white
titleLbl.textAlignment = .center
titleLbl.translatesAutoresizingMaskIntoConstraints = false
addSubview(titleLbl)
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[titleLbl]|", options: [], metrics: nil, views: ["titleLbl":titleLbl]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[titleLbl]|", options: [], metrics: nil, views: ["titleLbl":titleLbl]))
}
}
class BottomCollectionView:UICollectionView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return point.y > 0
}
}
Put two collection view inside one score view and try to generate parallel effect, you can able to achieve this kind of effect. Please find below link for reference.
Source
replace header image with your collection view.

UIButton inside a UIView subclass is not working, Swift

I've seen similar SO questions, the most promising ones advised to set .isUserInteractionEnabled = true and .bringSubview(toFront: subView) however both these don't seem to work in my case (not sure if I am using it correctly). I am neither able to set the backgroundColor and border for the view.
Please advise where I am going wrong.
I would like to call a function on click of a editButton which is inside a subview.
This is what I get when I run the code:
Here is the code:
class ProfilePhotoView: UIView{
var profileImage = UIImageView()
var editButton = UIButton()
var currentViewController : UIViewController
init(frame: CGRect, viewController : UIViewController){
self.currentViewController = viewController
super.init(frame: frame)
self.isUserInteractionEnabled = true
setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setup(){
profileImage.image = #imageLiteral(resourceName: "profilePlaceHolder")
editButton.setTitle("edit", for: .normal)
editButton.setTitleColor(Colors.curieBlue, for: .normal)
editButton.isUserInteractionEnabled = true
editButton.addTarget(self, action: #selector(editPhoto), for: .touchUpInside)
profileImage.translatesAutoresizingMaskIntoConstraints = false
editButton.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(profileImage)
self.addSubview(editButton)
let viewsDict = [ "profileImage" : profileImage,
"editButton" : editButton
] as [String : Any]
self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[profileImage]", options: [], metrics: nil, views: viewsDict))
self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-10-[profileImage]", options: [], metrics: nil, views: viewsDict))
self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[editButton]", options: [], metrics: nil, views: viewsDict))
self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[profileImage]-10-[editButton]", options: [], metrics: nil, views: viewsDict))
}
func editPhoto(){
Utils.showSimpleAlertOnVC(targetVC: currentViewController, title: "Edit Button Clicked", message: "")
}
}
ViewController using ProfilePhotoView
class ProfilePhotoViewVC: UIViewController {
var profilePhotoView : ProfilePhotoView!
//let imagePickerController = UIImagePickerController() // Initializing imagePicker
//var imagePickerDelegate = ProfilePhotoDelegate()
override func viewDidLoad() {
super.viewDidLoad()
let profilePhotoFrame = CGRect(x: 0, y: 0, width: 300, height: 800)
profilePhotoView = ProfilePhotoView(frame: profilePhotoFrame, viewController: self)
profilePhotoView.isOpaque = false
profilePhotoView.backgroundColor = Colors.lightGrey
profilePhotoView.layer.borderWidth = 1
profilePhotoView.layer.borderColor = Colors.iOSBlue.cgColor
profilePhotoView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(profilePhotoView)
view.bringSubview(toFront: profilePhotoView)
view.isUserInteractionEnabled = true
let viewsDict = [ "profilePhotoView" : profilePhotoView] as [String : Any]
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[profilePhotoView]", options: [], metrics: nil, views: viewsDict))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-10-[profilePhotoView]", options: [], metrics: nil, views: viewsDict))
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
As soon as you set this:
profilePhotoView.translatesAutoresizingMaskIntoConstraints = false
your profilePhotoView's width and height get set to 0, and you need to specify them with constraints. Add (==300) and (==800) to your visual formats to set them:
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[profilePhotoView(==300)]", options: [], metrics: nil, views: viewsDict))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-10-[profilePhotoView(==800)]", options: [], metrics: nil, views: viewsDict))
Also, consider using NSLayoutConstraint.activate() to activate the constraints instead of adding them to the view. iOS will add the constraints to the correct views for you. Also, options has a default value of [], so you can leave that off:
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(
withVisualFormat: "H:|-10-[profilePhotoView(==300)]",
metrics: nil, views: viewsDict)
)
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(
withVisualFormat: "V:|-10-[profilePhotoView(==800)]",
metrics: nil, views: viewsDict)
)

I want to add list of buttons in the same line in Swift 3.0

What I want is to add list of buttons (the number come from the service) to a uiview programmatically, so I think is I have to check if the space between the last button and the end of UIView is enough to add button or I have to go to the next line? Right?
Could you please help me on that?
Thanks,
Here you go. Button added with auto layout constraints so it will work the same in all size classes :)
var lastButton : UIButton? = nil
for i in 0...5 {
let button = UIButton()
button.backgroundColor = UIColor.green
button.setTitle("Button \(i)", for: .normal)
button.sizeToFit()
button.translatesAutoresizingMaskIntoConstraints = false
if i == 0 {
let viewComponents : [String : Any] = ["button" : button]
self.view.addSubview(button)
let horizontalConstraint = NSLayoutConstraint.constraints(withVisualFormat: "H:|-(20)-[button]", options: [], metrics: nil, views: viewComponents)
let verticalConstraint = NSLayoutConstraint.constraints(withVisualFormat: "V:|-(8)-[button]", options: [], metrics: nil, views: viewComponents)
self.view.addConstraints(horizontalConstraint)
self.view.addConstraints(verticalConstraint)
self.view.layoutIfNeeded()
lastButton = button
}
else {
if (lastButton != nil) {
self.view.addSubview(button)
let viewComponents : [String : Any] = ["button" : button, "lastButton" : lastButton!]
if (lastButton!.frame.maxX + 8 + button.bounds.size.width) > self.view.bounds.size.width {
let horizontalConstraint = NSLayoutConstraint.constraints(withVisualFormat: "|-(8)-[button]", options: [], metrics: nil, views: viewComponents)
let verticalConstraint = NSLayoutConstraint.constraints(withVisualFormat: "V:[lastButton]-(8)-[button]", options: [], metrics: nil, views: viewComponents)
self.view.addConstraints(horizontalConstraint)
self.view.addConstraints(verticalConstraint)
self.view.layoutIfNeeded()
lastButton = button
}
else {
self.view.addSubview(button)
let horizontalConstraint = NSLayoutConstraint.constraints(withVisualFormat: "[lastButton]-(8)-[button]", options: [.alignAllTop], metrics: nil, views: viewComponents)
self.view.addConstraints(horizontalConstraint)
self.view.layoutIfNeeded()
lastButton = button
}
}
}
}
The idea is using UICollectionView. You can implement it with following:
Subclass UICollectionViewCell to make your own cell which has the button
Subclass UICollectionViewFlowLayout, override
layoutAttributesForItem(at indexPath: IndexPath) which provides the attributes for each cell. You may need to pre-calculate the frame of button or cell in function prepare()
Create UICollectionView instance with the collectionviewLayout you just subclassed.

subclass of a generic UIViewController subclass isn't available in InterfaceBuilder [duplicate]

This question already has answers here:
Xcode 7 Swift 2 impossible to instantiate UIViewController subclass of generic UITableViewController
(2 answers)
Closed 6 years ago.
I have a generic UIViewController subclass that expects a type that is a subclass of UIView and also implements a specific protocol. I have a subclass of that generic UIViewController subclass. I have a view controller in a story board with it's type set to MyKeyboardAwareScrollViewController (the subclass of the generic UIViewController subclass). When I run my program I get a message:
Unknown class _TtC19ExploreCodeCoverage35MyKeyboardAwareScrollViewController in Interface Builder file.
Any ideas why/how to fix?
A protocol:
protocol MyProtocol{
func widgets() -> Void
func activeView() -> UIView?
}
A Generic View Controller Subclass (that requires a type that is a UIView implementing the protocol MyProtocol):
class KeyboardAwareScrollViewController<T:UIView where T:MyProtocol>: UIViewController {
private let titleView: UIView
private let contentView: T
private weak var scrollView: UIScrollView?
private weak var titleViewContainer: UIView?
init(titleView:UIView, contentView: T){
self.titleView = titleView
self.contentView = contentView
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
self.titleView = aDecoder.decodeObjectOfClass(UIView.self, forKey: "titleView")!
self.contentView = aDecoder.decodeObjectOfClass(T.self, forKey: "contentView")!
super.init(coder: aDecoder)
}
override func viewWillAppear(animated: Bool) {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardShown:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardHidden:", name: UIKeyboardWillHideNotification, object: nil)
}
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func keyboardShown(notification: NSNotification){
let kbSize = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
if let scrollView = scrollView{
let insets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: kbSize.height, right: 0.0)
scrollView.contentInset = insets
scrollView.scrollIndicatorInsets = insets
if let activeField = self.contentView.activeView(){
let convertedFrame = scrollView.convertRect(activeField.superview!.bounds, fromView: activeField.superview!)
let offsetFrame = CGRectOffset(convertedFrame, 0.0, 19.0)
let frame = CGRectInset(offsetFrame, 0.0, 17.0)
scrollView.scrollRectToVisible(frame, animated: false)
}
}
}
func keyboardHidden(notification: NSNotification){
if let scrollView = scrollView{
let insets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
scrollView.contentInset = insets
scrollView.scrollIndicatorInsets = insets
}
}
override func loadView() {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.brownColor()
let sv = UIScrollView()
sv.backgroundColor = UIColor.clearColor()
sv.opaque = false
sv.translatesAutoresizingMaskIntoConstraints = false
sv.alwaysBounceVertical = true
self.scrollView = sv
let titleContainer = buildTitleContainer(self.titleView)
view.addSubview(titleContainer)
view.addSubview(sv)
//peg scroll view to container.
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[scrollView]|", options:NSLayoutFormatOptions(rawValue: 0), metrics:nil, views: ["scrollView":sv]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[scrollView]|", options:NSLayoutFormatOptions(rawValue: 0), metrics:nil, views: ["scrollView":sv]))
let contentViewWrapper = UIView()
contentViewWrapper.translatesAutoresizingMaskIntoConstraints = false
contentViewWrapper.addSubview(self.contentView)
contentViewWrapper.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|->=0-[contentView]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["contentView": contentView]))
contentViewWrapper.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[contentView]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["contentView": contentView]))
sv.addSubview(contentViewWrapper)
let viewsDictionary = [
"scrollView": sv,
"view": view,
"contentViewWrapper": contentViewWrapper
]
//peg contentView to be at least as tall as view.
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[contentViewWrapper(>=view)]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary))
//peg contentView to be equal to host view width
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[contentViewWrapper(==view)]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary))
//peg title container to width of container.
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[titleContainer]|",
options:NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: ["titleContainer":titleContainer]))
//title view height.
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[titleContainer][contentView]",
options:NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: ["titleContainer":titleContainer, "contentView":self.contentView]))
self.view = view
self.titleViewContainer = titleContainer
}
func buildTitleContainer(titleView:UIView) -> UIView{
let titleContainer = UIView()
titleContainer.translatesAutoresizingMaskIntoConstraints = false
titleContainer.backgroundColor = UIColor.orangeColor()
titleContainer.addSubview(titleView)
titleContainer.setContentCompressionResistancePriority(1000.0, forAxis: .Vertical)
titleContainer.addConstraint(NSLayoutConstraint(item: titleView, attribute: .CenterY, relatedBy: .Equal, toItem: titleContainer, attribute: .CenterY, multiplier: 1.0, constant: 0.0))
titleContainer.addConstraint(NSLayoutConstraint(item: titleView, attribute: .CenterX, relatedBy: .Equal, toItem: titleContainer, attribute: .CenterX, multiplier: 1.0, constant: 0.0))
return titleContainer;
}
}
A UIView subclass that implements the protocol:
class MyContentView:UIView, MyProtocol, UITextFieldDelegate{
weak var myActiveView:UIView?
init(){
super.init(frame: CGRectZero)
self.backgroundColor = UIColor.clearColor()
self.opaque = false
self.translatesAutoresizingMaskIntoConstraints = false
let cells = Array(1...12).map{_ in cellView() }
var cellsDictionary: Dictionary<String, UIView> = [:], cellsVerticalFormat = "V:|-"
for (idx, cell) in cells.enumerate(){
self.addSubview(cell)
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-[cell]-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["cell": cell]))
let cellKey = "cell\(idx)"
cellsDictionary[cellKey] = cell
cellsVerticalFormat = cellsVerticalFormat + "[\(cellKey)]-"
}
cellsVerticalFormat = cellsVerticalFormat + "|"
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(cellsVerticalFormat, options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: cellsDictionary))
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func cellView() -> UIView{
let result = UIView()
result.backgroundColor = UIColor.magentaColor()
result.translatesAutoresizingMaskIntoConstraints = false
let text = UITextField()
text.translatesAutoresizingMaskIntoConstraints = false
result.addSubview(text)
text.delegate = self
result.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-[text]-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["text": text]))
result.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[text]-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["text": text]))
return result
}
func widgets() {
print("\(MyContentView.self) widgets")
}
func activeView() -> UIView? {
return myActiveView
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool{
myActiveView = textField.superview!
return true
}
func textFieldDidEndEditing(textField: UITextField) {
if myActiveView === textField.superview! {
myActiveView = nil
}
}
func textFieldShouldEndEditing(textField: UITextField) -> Bool {
return true
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return false
}
}
A KeyboardAwareViewController subclass:
class MyKeyboardAwareScrollViewController : KeyboardAwareScrollViewController<MyContentView>{
let myContentView: MyContentView = MyContentView()
let myTitleView: UILabel = {
let result = UILabel()
result.translatesAutoresizingMaskIntoConstraints = false
result.text = "Pretend This Says\nCohn-Reznick"
result.numberOfLines = 0
result.textAlignment = .Center
return result
}()
required init?(coder aDecoder: NSCoder) {
super.init(titleView: myTitleView, contentView:myContentView)
}
}
Answer is here: Xcode 7 Swift 2 impossible to instantiate UIViewController subclass of generic UITableViewController
TLDR; Objective-c and InterfaceBuilder can't "see" generic swift classes.

AutoLayout constraints for programmatically added buttons in a scrollView

I'm struggling with making autolayout work for a horizontal scrollview populated with an unknown number of buttons (in this case the number is stored in sceneCount).
I've tried to approach the problem in other ways as well, but this seems to be the closest I could get to a result (no contradictory constraints). Unfortunately I only get a white screen at runtime with no errors.
This is how I wish my scrollView would look like.
Hope you guys can spot the problem!
import UIKit
class ViewController: UIViewController {
let sceneCount = 4
var button = UIButton.buttonWithType(UIButtonType.System) as! UIButton
var buttons: [UIButton] = [UIButton]()
func makeLayout(){
//Make the content view
let view1 = UIScrollView()
view1.setTranslatesAutoresizingMaskIntoConstraints(false)
view1.backgroundColor = UIColor.redColor()
//Make the scroll view
let view2 = UIScrollView()
view2.setTranslatesAutoresizingMaskIntoConstraints(false)
view2.backgroundColor = UIColor(red: 0.75, green: 0.75, blue: 0.1, alpha: 1.0)
view.addSubview(view1)
view2.addSubview(view1)
//Create the buttons
for i in 0...(sceneCount-1) {
button.setTranslatesAutoresizingMaskIntoConstraints(false)
button.addTarget(self, action: Selector("scene\(i)Pressed"), forControlEvents: UIControlEvents.TouchUpInside)
button.setBackgroundImage(UIImage(named: "Scene\(i+1)"), forState: UIControlState.Normal)
button.sizeThatFits(CGSizeMake(40, 40))
buttons.append(button)
view1.addSubview(button)
}
//set horizontal spacing between the buttons
for i in 1...(sceneCount-2) {
var button1 = UIButton.buttonWithType(UIButtonType.System) as! UIButton
var button2 = UIButton.buttonWithType(UIButtonType.System) as! UIButton
button1 = buttons[i - 1]
button2 = buttons[i]
var dictionary1 = [String: UIButton]()
dictionary1.updateValue(button1, forKey: "scene1")
dictionary1.updateValue(button2, forKey: "scene2")
view1.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[scene1]-[scene2]", options: NSLayoutFormatOptions(0), metrics: nil, views: dictionary1) as [AnyObject])
}
//set vertical spacing for all buttons
for i in 0...(sceneCount-1) {
var button1 = UIButton.buttonWithType(UIButtonType.System) as! UIButton
button1 = buttons[i]
var dictionary2 = [String: UIButton]()
dictionary2.updateValue(button1, forKey: "button1")
view1.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[button1]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: dictionary2) as [AnyObject])
}
//set horizontal distance to container for first button
if sceneCount > 0 {
var buttonFirst: UIButton = buttons[0]
var dictionary3 = [String: UIButton]()
dictionary3.updateValue(buttonFirst, forKey: "buttonFirst")
view1.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[buttonFirst]", options: NSLayoutFormatOptions(0), metrics: nil, views: dictionary3) as [AnyObject])
}
//set horizontal distance to container for last button
if sceneCount > 0 {
var buttonLast: UIButton = buttons[sceneCount-1]
var dictionary4 = [String: UIButton]()
dictionary4.updateValue(buttonLast, forKey: "buttonLast")
view1.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[buttonLast]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: dictionary4) as [AnyObject])
}
}
override func viewDidLoad() {
super.viewDidLoad()
makeLayout()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I managed to make your code work with three major changes as follows:
1 - Your content view needs to be of type UIView
let view1 = UIView()
This view will hold a UIScrollView (view2) and they have to have their constraints set. On view1 you will be able to set the size of the scroll view, that's why I made view 2 with 0 distance from the super view.
view1.addSubview(view2)
var dictionary = [String: UIView]()
dictionary.updateValue(view1, forKey: "view1")
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[view1]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: dictionary) as [AnyObject])
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[view1]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: dictionary) as [AnyObject])
dictionary = [String: UIView]()
dictionary.updateValue(view2, forKey: "view2")
view1.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[view2]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: dictionary) as [AnyObject])
view1.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[view2]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: dictionary) as [AnyObject])
2 - As you were using the same reference to create new buttons, the final array would have the same button in every position.
I moved the initialization of each button inside the for that creates buttons.
var button = UIButton.buttonWithType(UIButtonType.System) as! UIButton
view2.addSubview(button)
3- After that I just updated your constraints to be inserted between the buttons and the scroll view (view2).
And here is the final code:
import UIKit
class ViewController: UIViewController {
let sceneCount = 4
var buttons: [UIButton] = [UIButton]()
func makeLayout(){
//Make the content view
let view1 = UIView()
view1.setTranslatesAutoresizingMaskIntoConstraints(false)
view1.backgroundColor = UIColor.redColor()
//Make the scroll view
let view2 = UIScrollView()
view2.setTranslatesAutoresizingMaskIntoConstraints(false)
view2.backgroundColor = UIColor(red: 0.75, green: 0.75, blue: 0.1, alpha: 1.0)
view.addSubview(view1)
view1.addSubview(view2)
var dictionary = [String: UIView]()
dictionary.updateValue(view1, forKey: "view1")
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[view1]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: dictionary) as [AnyObject])
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[view1]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: dictionary) as [AnyObject])
dictionary = [String: UIView]()
dictionary.updateValue(view2, forKey: "view2")
view1.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[view2]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: dictionary) as [AnyObject])
view1.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[view2]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: dictionary) as [AnyObject])
//Create the buttons
for i in 0...(sceneCount-1) {
var button = UIButton.buttonWithType(UIButtonType.System) as! UIButton
button.setTranslatesAutoresizingMaskIntoConstraints(false)
button.addTarget(self, action: Selector("scene\(i)Pressed"), forControlEvents: UIControlEvents.TouchUpInside)
button.setBackgroundImage(UIImage(named: "Scene\(i+1)"), forState: UIControlState.Normal)
button.sizeThatFits(CGSizeMake(40, 40))
buttons.append(button)
view2.addSubview(button)
}
//set vertical spacing for all buttons
for i in 0...(sceneCount-1) {
var button1 = buttons[i]
var dictionary2 = [String: UIButton]()
dictionary2.updateValue(button1, forKey: "button1")
view2.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[button1]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: dictionary2) as [AnyObject])
}
//set horizontal distance to container for first button
if sceneCount > 0 {
var buttonFirst: UIButton = buttons[0]
var dictionary3 = [String: UIButton]()
dictionary3.updateValue(buttonFirst, forKey: "buttonFirst")
view2.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[buttonFirst]", options: NSLayoutFormatOptions(0), metrics: nil, views: dictionary3) as [AnyObject])
}
//set horizontal spacing between the buttons
for i in 1...(sceneCount-1) {
var button1 = buttons[i - 1]
var button2 = buttons[i]
var dictionary1 = [String: UIButton]()
dictionary1.updateValue(button1, forKey: "scene1")
dictionary1.updateValue(button2, forKey: "scene2")
view2.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[scene1]-[scene2]", options: NSLayoutFormatOptions(0), metrics: nil, views: dictionary1) as [AnyObject])
}
//set horizontal distance to container for last button
if sceneCount > 0 {
var buttonLast: UIButton = buttons[sceneCount-1]
var dictionary4 = [String: UIButton]()
dictionary4.updateValue(buttonLast, forKey: "buttonLast")
view2.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[buttonLast]-|", options: NSLayoutFormatOptions(0), metrics: nil, views: dictionary4) as [AnyObject])
}
}
override func viewDidLoad() {
super.viewDidLoad()
makeLayout()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}

Resources