I currently have a UICollectionView table with cells, I am trying to allow each cell that is created to have their own unique view controller. For example, when the UICollectionViewCell is tapped, the view controller shows for that specific cell. I know that I can create one viewcontroller and perform a segue to only that one view controller. That only covers one cell... If the user creates 25 cells... how do I make a view controller for each cell without making a segue? The code below is to create a cell.
// MARK: Create collection View cell with title, image, and rounded border
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ChatCell
let object = objects[indexPath.row]
cell.chatLabel.text = object.title ?? ""
cell.chatImage.image = object.image
if let chatImagePath = object.imagePath {
if let imageURL = URL(string: chatImagePath) {
cell.chatImage.sd_setImage(with: imageURL)
}
}
cell.layer.borderWidth = 0.5
cell.layer.borderColor = UIColor.darkGray.cgColor
cell.layer.masksToBounds = true
cell.layer.cornerRadius = 8
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return objects.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let itemWidth = photoCollectionView.bounds.width
let itemHeight = photoCollectionView.bounds.height / 2
return CGSize(width: itemWidth, height: itemHeight)
}
In the comments under your question, you clarified that you really only have one basic type of view controller that you're transitioning to, but you want to make sure that you supply the correct information to it on the basis of which cell of the collection view you tapped on.
There are two basic approaches:
Easiest, in IB, create a segue from the collection view cell to the next scene in the storyboard, and then implement prepare(for:sender:) in the originating scene to pass whatever you need to that next scene.
For example, you might have a prepare(for:sender:) that does something like:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let indexPath = collectionView?.indexPathsForSelectedItems?.first, let destination = segue.destination as? DetailsViewController {
destination.object = objects[indexPath.item]
}
}
Now, this makes a ton of assumptions (e.g. that my collection view has an array, objects, that my destination view controller is a DetailsViewController, that it has some object property, etc.), but hopefully it illustrates the basic idea.
You said you didn't want to use a segue. I'm not sure why, but, if you really don't want segue, then just use implement collectionView(_:didSelectItemAt:) and initiate the transition programmatically, however you want.
Related
I'm new to swift and building iOS Application from the scratch (using swift 4) and want to do something like below.
1. Implement Multiple cell selections in UICollectionView,
2. Pass selected cells data to Server.
Please anyone can help me, how to do that? Tell me the process and supporting articles to do that.
Below is reference Image. Thanks in Advance.
Well, the best way to handle multiple selections in UICollectionView
Enable Multiple Selection
myCollectionView.allowsMultipleSelection = true
put this code in your cell awakeFromNib
override func awakeFromNib() {
super.awakeFromNib()
let view = UIView(frame: bounds)
self.backgroundView = view
let coloredView = UIView(frame: bounds)
coloredView.backgroundColor = UIColor.red
self.selectedBackgroundView = coloredView
}
you can get the selected indexPath items
let items = myCollectionView.indexPathsForSelectedItems
This basic example. You can change as per your data.
When you select any cell then you need to check that selected cell is already selected before or not.
If not then add selected cell indexPath in indexArray and selected cell value in valueArray.
If current selected cell is previously selected then remove indexPath from indexArray and also remove selected cell value from valueArray
on continue button press pass arrSelectedData to server or next screen.
Define below 3 array.
var arrData = [String]() // This is your data array
var arrSelectedIndex = [IndexPath]() // This is selected cell Index array
var arrSelectedData = [String]() // This is selected cell data array
//UICollectionView Delegate & DataSource
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.arrData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell : CollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
if arrSelectedIndex.contains(indexPath) { // You need to check wether selected index array contain current index if yes then change the color
cell.vw.backgroundColor = UIColor.red
}
else {
cell.vw.backgroundColor = UIColor.lightGray
}
cell.layoutSubviews()
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: 100)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.item)!")
let strData = arrData[indexPath.item]
if arrSelectedIndex.contains(indexPath) {
arrSelectedIndex = arrSelectedIndex.filter { $0 != indexPath}
arrSelectedData = arrSelectedData.filter { $0 != strData}
}
else {
arrSelectedIndex.append(indexPath)
arrSelectedData.append(strData)
}
collectionView.reloadData()
}
}
You can write the code like this to Enable Multiple Selection :-
yourCollectionViewName.allowsMultipleSelection = true
then you can Do it like this to see the cell Selected -
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
var cell = collectionView.cellForItemAtIndexPath(indexPath)
if cell?.selected == true {
cell?.backgroundColor = UIColor.orangeColor()
}
}
To Deselect You can do something Like this -
func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
var cell = collectionView.cellForItemAtIndexPath(indexPath)
cell?.backgroundColor = UIColor.clearColor()
}
Enable Multiple Selection
collectionView.allowsMultipleSelection = true
Overrider isSelected property of collectionViewCell.
override var isSelected: Bool {
didSet {
if self.isSelected {
//You can change this method according to your need.
setSelected()
}
else {
//You can change this method according to your need.
setUnselected()
}
}
}
func setSelected(){
bgView.layer.borderWidth = 4
bgView.layer.borderColor = UIColor.Palette.darkBlue.cgColor
bgView.backgroundColor = .blue.withAlphaComponent(0.2)
}
func setUnselected(){
bgView.layer.borderWidth = 0
bgView.backgroundColor = .white
}
You can print selected cell's indexPath
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(collectionView.indexPathsForSelectedItems)
}
Well, to achieve a thing like that, you need to mainly perform the following tasks
Whenever user clicks on a particular cell, you need to change the background colour for that item in the didSelectItemAt delegate method of UICollectionView
Now to send that data to server, you need an array to store all the selected cells and then send that array to server . You can perform the same in didSelectItemAt method as well
I can show you a prototype of what the function will look like:
Let's assume you have an array named arrayForPopulating for populating data inside Collection View and we have array named finalSelections which consist of names of all the selections that user made
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
// Change the background colour of the cell here
cell.contentView.backgroundColor = UIColor.red
// Add the selected cell's data to the array
finalSelections.append(arrayForPopulating[indexPath.row])
}
Now you can send you finalSelections array to the server !
How can I create a tag fields like the below attached screenshot in iOS.
tag text shall be taken from the user input.
Swift 3 Xcode 8.3.2
Its easy. No need to use any framework for this, You can create your own view.
Suppose you have a ViewController on which you want to add skills for particular user. So on that view you will be having one TextField to accept the skill name and a collectionView to show all the skills entered by the user.Check image for more clear view.
Create skills array which you need to pass to your collectionView delegate methods.
var skillArray = [String]()
Now whenever you add anything inside your textfield and on press of return button on keyboard you need to add that skill name in your skills array and reload your collectionView.
skillArray.append(textField.text)
collectionView.reloadData()
Next add collectionview methods:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.skillArray.count
}
// make a cell for each cell index path
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let skillCell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath as IndexPath) as! SkillCollectionViewCell
skillCell.skillLabel.text = skillArray[indexPath.row]
skillCell.deleteAction = {
self.skillArray.remove(at: indexPath.row)
self.collectionView.reloadData()
}
return skillCell
}
Final step is to add collectionViewFlowLayout methods.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// handle tap events
print("You selected cell #\(indexPath.item)!")
}
func collectionView(_ collectionView: UICollectionView,layout collectionViewLayout: UICollectionViewLayout,sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize{
let widthText = self.skillArray[indexPath.item]
let font = UIFont(name: "Helvetica", size:18)
let fontAttributes = [NSFontAttributeName: font] // it says name, but a UIFont works
let myText = widthText
let size = (myText as NSString).size(attributes: fontAttributes)
return CGSize(width: size.width+28, height:30)
}
You can use more Designs with using user input and handling the tag by using cell index of collection view for more designs follow the link -
https://www.cocoacontrols.com/search?q=flowlayout
I recommend UICollectionView to implement Tags. It's less code, less overhead and easy to implement.
Follow this link for guidance :
https://codentrick.com/create-a-tag-flow-layout-with-uicollectionview/
This is how it looks like. Please note it is flexible too.
Hope this will help you
I have set up a ViewController with UICollectionViewCells, inside of a navigation controller. I want to be able to click on the cells and then have the user be taken to a new controller depending on which cell is selected (different controller for each cell). I want the navigation bar to still appear in the new controller, and have a back button that will take the user back to the original ViewController. I have the following code inside the initial view controller to set up the collection view cells:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: playlistCellId, for: indexPath) as! playlistCoverCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: 100)
}
I also register the cells correctly in viewDidLoad. What function do I use to perform an action when selecting a cell?
you have to use:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.row == 0 {
let viewController = UIViewController() // or your custom view controller
self.navigationController?.pushViewController(viewController, animated: true)
}
else if indexPath.row == 1 {
// and so on....
}
}
Tells the delegate that the item at the specified index path was
selected. The collection view calls this method when the user
successfully selects an item in the collection view. It does not call
this method when you programmatically set the selection.
you can try UICollectionViewDelegate in the function
enter image description here
you can use indexPath to get elements of the current click;
push to next viewController you have to have navigationViewController, if navigationController is nil, you can try protocol or block. Sorry, my English is not good, maybe grammar is wrong.
I want to make a scrollable list of images like instagram with 4 columns. I created a collection view with image views http://prntscr.com/d15rnx . But I get this result - http://prntscr.com/d15tsq
code -
// MARK: - UICollectionViewDataSource protocol
// tell the collection view how many cells to make
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.items.count
}
// make a cell for each cell index path
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! MyCollectionViewCell
// Use the outlet in our custom class to get a reference to the UILabel in the cell
cell.imageView.image = UIImage(named: "main/p\(self.items[indexPath.item].code)/main/main.jpg")
print("main_card_images/p\(self.items[indexPath.item].code)/main/main")
return cell
}
// MARK: - UICollectionViewDelegate protocol
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// handle tap events
print("You selected cell #\(indexPath.item)!")
print(self.items[indexPath.item])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let yourNextViewController = (segue.destination as! ViewControllerCard)
let indexPath = collectionView.indexPath(for: sender as! UICollectionViewCell)
yourNextViewController.mainCardImage = self.items[(indexPath?.item)!]
}
Make sure you have set inter cell spacing to 0 and then all you have to do is implement:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return(collectionView.bounds.size.width/4,collectionView.bounds.size.height/4)
}
This will resize all cells to be equal to 1/4 the UICollectionView width and thus create 4 cells in each row(1/4 the height value is just cause i like symmetry in these types of UI). If there appears to be any less number of cells than that, its because the inter-item spacing has not been set to 0. If you want inter-item spacing, just subtract that value from the width value returned in the above function
Alternatively
You can implement your own custom flow layout but the above solution is far more simpler.
I have found it easiest to configure my collection view via the setting in Interface Builder. For a collection view with a flow layout here are the settings I use for the insets. I have found the item size width: 125.8 and height: 125.8 to give me the best size for displaying on most devices in portrait or landscape.
Here's what my collection view looks like.
You can of course set this programmatically using UICollectionViewDelegateFlowLayout or as Rikh suggests by subclassing CollectionViewFlowLayout
I currently have a collection view set up to display a dynamic number of objects in its view. Each cell displays an image from the corresponding object. When a cell is tapped, it triggers a segue to the next view in the hierarchy. The cell's corresponding object is passed to the next view However, I am noticing that when I return the view with the collection, the ordering of cells has changed, and now, when I tap one to got the next view, its properties are from objects of other cells.
Below are my methods of the UICollectionView:
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return objectsCount
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Chow Object Reuse ID", forIndexPath: indexPath) as UICollectionViewCell
cell.backgroundColor = UIColor.whiteColor()
var imageView:UIImageView = UIImageView()
imageView.frame = CGRect(x: 0, y: 0, width: 90, height: 90)
//Since loading of images is a time-intensive task, all the thumbnails
//may have not been fetched yet.
if (imageThumbsArray.count == objectsCount) { //Eventually, a more elegant fix will be needed.
imageView.image = imageThumbsArray[indexPath.row]
}
cell.addSubview(imageView)
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
self.objectToBePassed = self.chowObjectsArray[indexPath.row] as PFObject
self.performSegueWithIdentifier("Show Chow Details", sender: self)
}
I also call self.PastObjectsCollection.reloadData()
Why is the reordering and mixing up of cells happening?
Thanks,
Siddharth