I just got into ios swift and are now trying to switch between listview and bigger listview.
What I have now is a normal listview. What I want to do is to create a button that toggles between listview and "bigger" listview eg bigger image in every list item like in Instagram.
Is this done by creating two seperate viewcontrollers?
Your question is not clear at all.
It is clearly possible to create that with two ViewControllers performing
a Segue passing the Object selected to the next view.
Alternative , if what you are using is a ScrollView , you can use the existing zoom methods (zoomToRect).
What I did to make it work is have 2 UIButton connect with 2 func within the main collectionview class as describe below
// Use to define whether displaying grid view or block view
// useful when you use nib files for cells (see the 3rd code example)
// true by default
var isGridView = true
loadGridView () {
isGridView = true
collectionView?.performBatchUpdates({
// load or setup for gridlayout
}, completion: nil)
collectionView?.reloadData()
}
and
loadBlockView () {
isGridView = false
collectionView?.performBatchUpdates({
// load or setup for blocklayout
}, completion: nil)
collectionView?.reloadData()
}
For instance, you can visit this article for more information
!!! If you're using nib for cells, you will need to register all nibs and its class also be aware of
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell:UICollectionViewCell
if(isGridView) {
let gridCell = collectionView.dequeueReusableCellWithReuseIdentifier(cellId, forIndexPath: indexPath) as! CustomGridViewCellClass
// some setup
cell = gridCell
} else {
let blockCell = collectionView.dequeueReusableCellWithReuseIdentifier(cellId, forIndexPath: indexPath) as! CustomBlockViewCellClass
// some setup
cell = blockCell
}
return cell
}
Related
I have 10 pictures that I show in collection view. There are also two buttons that should sort this one collection view. When I launch the application 1-5 cells and other hidden ones should be shown. when I click on the second button, these cells should be hidden and another will appear. How can I implement this?
ViewController
let practice = true
#IBAction func theoryButtonAction(_ sender: UIButton) {
practice = false
collectionView.reloadData()
}
#IBAction func practiceButtonAction(_ sender: UIButton) {
practice = true
collectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: GameCollectionViewCell.identifier, for: indexPath) as? GameCollectionViewCell else {
return UICollectionViewCell()
}
let game = gamesList[indexPath.row]
cell.gameLabel.text = game.name.localized
cell.gameLabel.roundCorners(.allCorners, radius:cell.gameLabel.frame.height / 2 )
cell.contentView.roundCorners(.allCorners, radius: 50.0)
/*
if practice = true. Hide cell 1-5 and show 5-10
if practice = false Hide cell 5-10 and show 1-5
*/
return cell
}
Your collection view has a backing store of data (generally an array). This backing store does not have to always be the full backing store. Simply create other backing stores which contain the data you want to display at this particular moment.
I read similar questions such as how to have multiple collection view in multiple table view cells and I connected my collection views cells and use identifier names for them but I don't know why I receive this Error:
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'could not dequeue a view of kind: UICollectionElementKindCell with identifier extera_infoCollectionViewCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
* First throw call stack:
**Remember that I read Similar questions and the first table view cell with collection view working well and the problem is for second one **
here is my code for main view controller that has a table view and the table view has two cells
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == fieldOfActivityCell().fieldofActivitiesCollectionView {
let fullfields : String = self.adv.resultValue[0].work_field!
let fullfieldsArr : [String] = fullfields.components(separatedBy: ",")
print(fullfieldsArr)
return fullfieldsArr.count
} else {
let extera_infofields : String = self.adv.resultValue[0].extera_info!
let extera_infofieldsArr : [String] = extera_infofields.components(separatedBy: ",")
print(extera_infofieldsArr)
return extera_infofieldsArr.count
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == fieldOfActivityCell().fieldofActivitiesCollectionView {
let fieldsCells = collectionView.dequeueReusableCell(withReuseIdentifier: "fieldOfActivityCollectionViewCell", for: indexPath) as! fieldOfActivityCollectionViewCell
let fullfields : String = self.adv.resultValue[0].work_field!
let fullfieldsArr : [String] = fullfields.components(separatedBy: ",")
fieldsCells.title.text = fullfieldsArr[indexPath.row]
return fieldsCells
}
else {
let extera_infoCells = collectionView.dequeueReusableCell(withReuseIdentifier: "extera_infoCollectionViewCell", for: indexPath) as! extera_infoCollectionViewCell
let extera_info : String = self.adv.resultValue[0].extera_info!
let extera_infoArr : [String] = extera_info.components(separatedBy: ",")
extera_infoCells.infoText.text = extera_infoArr[indexPath.row]
return extera_infoCells
}
}
and here is the table view codes in same view controller:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let fieldCell = self.showAdvTableView.dequeueReusableCell(withIdentifier: "fieldOfActivityCell", for: indexPath) as! fieldOfActivityCell
return fieldCell
} else {
let fieldCell = self.showAdvTableView.dequeueReusableCell(withIdentifier: "extera_infoCell", for: indexPath) as! extera_infoCell
return fieldCell
}
here is table view first cell class:
class fieldOfActivityCell: UITableViewCell {
#IBOutlet weak var fieldofActivitiesCollectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
if let flowLayout = fieldofActivitiesCollectionView.collectionViewLayout as? UICollectionViewFlowLayout { flowLayout.estimatedItemSize = CGSize.init(width: 1.0, height: 1.0) }
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
extension fieldOfActivityCell {
func setCollectionViewDataSourceDelegate
<D: UICollectionViewDelegate & UICollectionViewDataSource>
(_ dataSourceDelegate:D , forRow row : Int )
{
fieldofActivitiesCollectionView.delegate = dataSourceDelegate
fieldofActivitiesCollectionView.dataSource = dataSourceDelegate
fieldofActivitiesCollectionView.reloadData()
}
}
and here is the second tableview cell class:
#IBOutlet weak var extra_infoCollectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
if let flowLayout = extra_infoCollectionView.collectionViewLayout as? UICollectionViewFlowLayout { flowLayout.estimatedItemSize = CGSize.init(width: 1.0, height: 1.0) }
}
}
extension extera_infoCell {
func setCollectionViewDataSourceDelegate
<D: UICollectionViewDelegate & UICollectionViewDataSource>
(_ dataSourceDelegate:D , forRow row : Int )
{
extra_infoCollectionView.delegate = dataSourceDelegate
extra_infoCollectionView.dataSource = dataSourceDelegate
extra_infoCollectionView.reloadData()
}
}
First step: using Tags - you just need to use tag for them and use if else to choose which collection view has selected with tag so the answer is this :
if collectionView.tag == 1 {
do some thing//////
}else {
do some thing else}
and you should use this in both cellForRowAtIndexPath and numberOfRows methods you can use this for table view too
Second step: you have to change the name of 'collection view' that you are dequeueing inside the cellForRowAt method in CollectionView data source:
if collectionView.tag == 1 {
let cell = yourFirstCollectionView.dequeueReusableCell(...) as yourCell
....
return cell
} else {
let cell = yourSecondCollectionView.dequeueReusableCell(...) as yourCell
....
return cell
}
According to your error your reuse identifier doesn't match any cell in your storyboard. Click on your extera_info collectionView cell in interface builder. Select the attributes inspector tab. Under reuse identifier make sure you put in extera_infoCollectionViewCell
If you take the other tableview cell In different class , with NSObject features of storyboard it can help you , And it is easy to maintain .
Saeed's tag option above is likely the simplest answer, but found his description a little short so adding a more complete answer below for those who've never used tags before...
If abiding by MVC and placing collectionView dataSource methods inside the UITableView class (instead of inside the UITableViewCell classes), and wanting to avoid this " error:
Each Collection View you use will need its own dequeueReusableCell identifier:
In interface-builder, name all your identifiers for your collection view cells. CatPicCell & DogPicCell for instance.
In your CellForItemAt collectionView method, set up if-statements or switch statement such that each reuse identifier is set equal to the identifiers you created in interface-builder (step 1). If using switch/case, your value can be set to collectionView.tag. Tags can be numbered to identify each different collectionView. The tags are like turning your set of collectionViews into a dictionary or array, such that each collectionView gets its own unique key/index.
Go back into interface-builder, and go into your storyboard and select each collection view (each of which should be inside its own tableView cell). In Xcode's "attribute inspector" scroll down to the "View" section and 3 spaces down (Xcode 11, Swift 5) you'll see a field called "Tag". Assign an integer value to that collection view, and then repeat this process for each collection view which is going to be embedded in your UITableView cells.
Once you have all the collection views tagged with unique integers, you simply set your cases to the integers, and give each dequeueReusableCell identifier the same integer index as you provided in the storyboard.
Now when you tableView cell calls on the collectionView you've outletted in the TableViewCell classes, it will be able to acquire the proper dequeueReusable ID. You can put your data inside each switch case.
Voila, you now have ONE collectionView datasource set of required methods, but serving ALL of your collection views. EVEN BETTER, when someone expands the project and adds another collectionView it will be as easy as adding another case to the switch and identifier in the storyboard.
Example code could look like this:
// I need a switch statement which will set the correct (of the 3 collectionViews) dequeueReusable IDENTIFIER for the collectionView
switch collectionView.tag {
//if tableView is doing cell == 1, then "CatsCell"
//if ... cell == 3, then "DogsCell"
//if ... cell == 5, then "BirdsCell"
case 1:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CatsCell", for: indexPath) as! CatsCVCell
// put your required data here
return cell
case 3:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DogCell", for: indexPath) as! DogsCVCell
// example data
let dogs = dogController.fetch()
cell.name = dogs[indexPath.item].dogName
if let image = UIImage(data: groups[indexPath.item].image!) {
cell.image = image
}
return cell
case 5:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BirdCell", for: indexPath) as! BirdCVCell
// put data code here for birds collection view cells
return cell
default:
return UICollectionViewCell() // or write a fatalError()
}
note: you have two options for your default to the switch statement...
1. like above, a generic but empty cell instance
2. throw an error. The error should never throw bc you'll have all the cases, but an error could occur if someone else improves your code and add another collectionView but forgets to to add the switch case-- so make your error statement explain what's wrong precisely.
Hello I am trying build a simple chat application where users can send messages and photos. I am having a hard time in figuring out the best way to select and delete multiple messages on long press on a single message.
I have used collection view to display the page. Right now I am using collection view didSelect method to click on the side of chat bubble image view and able to get select button for that particular cell. But, I am not able append checkbox button for every message. I also cannot long press on the chat bubble image view.
I also tried imageview tap on chat bubble but with this I need to reload the collection view. Is there a best way of implementing delete multiple messages?
Any help is appreciated
Thanks
Below is the sample code
code for changing the checkbox image of particular cell.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
inputTextField.endEditing(true)
let cell: ChatLogMessageCell? = collectionView.cellForItem(at: indexPath) as! ChatLogMessageCell?
cell?.checkbox.isHidden = false
selectAll = true
if cell?.isSelected == true{
cell?.checkbox.image = UIImage(named: "checkedimage")
}else{
cell?.checkbox.image = UIImage(named: "uncheckedimage")
}
code to tap on chat bubble to append checkbox button to all cells.
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: chatcellId, for: indexPath) as! ChatLogMessageCell
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.imageTapped))
cell.bubbleImageView.addGestureRecognizer(tapGesture)
cell.bubbleImageView.isUserInteractionEnabled = true
if selectAll == true{
cell.checkbox.isHidden = false
}else{
cell.checkbox.isHidden = true
}}
When chat bubble is tapped collection view is reloaded to append the checkbox button to all cells
func imageTapped(){
selectAll = true
self.collectionView?.reloadData()
}
What I am finally trying to do is select and delete messages like whatsapp or iMessage (Above code is close to iMessage functionality) does. So I am completely open for complete code changes too. Thanks.
updated Code
override func viewDidLoad()
{
super.viewDidLoad()
let lpgr = UILongPressGestureRecognizer(target: self,
action: #selector(handleLongPress))
lpgr.minimumPressDuration = 0.5
lpgr.delaysTouchesBegan = true
lpgr.delegate = self
self.collectionView?.addGestureRecognizer(lpgr)
}
func handleLongPress(gestureReconizer: UILongPressGestureRecognizer) {
let p = gestureReconizer.location(in: self.collectionView)
let indexPath = self.collectionView?.indexPathForItem(at: p)
if let index = indexPath {
let cell: ChatLogMessageCell? = collectionView?.cellForItem(at: index) as! ChatLogMessageCell?
self.collectionView?.allowsMultipleSelection = true
for cell in collectionView!.visibleCells as! [ChatLogMessageCell] {
let indexPath = collectionView?.indexPath(for: cell as ChatLogMessageCell)
cell.checkbutton.isHidden = false
if selectedMsgs.contains((messages?[((indexPath)?.item)!])!) {
cell?.checkbox.image = UIImage(named: "checkedimage")
}
else {
cell?.checkbox.image = UIImage(named: "uncheckedimage")
}
}
} else {
print("Could not find index path")
}
}
On long press check boxes appear on all visible cells, but tap on chat bubble is not working.
You should attach a UILongPressGestureRecognizer to each cell in the collectionview, and set the UICollectionviewcontroller as the target for each of these recognizers. Then, when any one of them fires, set a custom property of your CollectionViewController (maybe name it editing or something) to true. Then fetch all the visible cells with the UICollectionView's visibleCells function.
In your UICollectionViewCell subclass, you should have some custom property getter/setter methods (maybe -editing and -setEditing:(BOOL)) which you can call now as you iterate through the cells in visibleCells. Within your -setEditing:(BOOL) function, you can add and remove the checkbox UIButton as you please. You'll also want to set the UICollectionView controller as the target of this UIButton, and within the UICollectionViewController, keep track of which cells are selected so when the user hits the "Delete" button, you know which messages to delete.
I would also recommend checking out https://github.com/jessesquires/JSQMessagesViewController/, which does all this logic for you.
I make a network call in ViewDidLoad to get objects (first 25), then I make another call in willDisplayCell to get the rest of the objects. I'm Using PINReMoteImage in the code below. It works, but the problem is that as you scroll through the collection view, a cell will have one picture then another picture will appear over it. How can I improve this UI? I thought updateWithProgress was supposed to deal with this by using a blur until the image is loaded but it doesn't seem to be working?
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! PinCollectionViewCell
if let pinImageURL = self.pins[indexPath.row].largestImage().url {
cell.pinImage?.pin_updateWithProgress = true
cell.pinImage?.pin_setImageFromURL(pinImageURL, completion: ({ (result : PINRemoteImageManagerResult) -> Void in
if let image = result.image {
self.imageArray.append(image)
}
}))
}
The problem is that I'm reusing cells, but I wasn't resetting the image. So I simply added cell.pinImage?.image = nil to the above. Once I found out that was the problem I added an UIActivityViewIndicatorViewto the cell and stop it when the image comes in.
Let's say you set up a bunch of image views inside a UICollectionView's cells (from an array of image names) and make their alpha 0.5 by default when you set up the items.
Then you make the image view's alpha to 1.0 in the didSelectItemAtIndexPath func, so it becomes alpha 1 when the user taps.
This works when the user taps a cell, but it does not persist if the user scrolls, because the cell is being re-used by the UI on some other level.
The result is another cell farther down the way (when scrolling) becomes alpha 1.0 and the original cell you selected reverts back to its previous alpha 0.5 appearance.
I understand that this is all done to make things more efficient on the device, but I still have not figured out how to make it work properly where the selected item persists.
ANSWER
Apple does provide a selectedBackgroundView for cells that you can use to change the background color, shadow effect, or outline etc. They also allow you to use an image inside the cell with a "default" and "highlighted" state.
Both of those methods will persist with the selection properly.
However, if you wish to use attributes or different elements than one of those provided for indicating your selected state, then you must use a separate data model element that includes a reference to the currently selected item. Then you must reload the viewcontroller data when the user selects an item, resulting in the cells all being redrawn with your selected state applied to one of the cells.
Below is the jist of the code I used to solve my problem, with thanks to Matt for his patience and help.
All of this can be located inside your main UICollectionView Controller class file, or the data array and struct can be located inside their own swift file if you need to use it elsewhere in the project.
Data and data model:
let imagesArray=["image1", "image2", "image3", ...]
struct Model {
var imageName : String
var selectedState : Bool
init(imageName : String, selectedState : Bool = false){
self.imageName = imageName
self.selectedState = selectedState
}
}
Code for the UICollectionView Controller
// create an instance of the data model for images and their status
var model = [Model]()
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// build out a data model instance based on the images array
for i in 0..<imagesArray.count {
model.append(Model(imageName: imagesArray[i]))
// the initial selectedState for all items is false unless otherwise set
}
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imagesArray.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// when the collectionview is loaded or reloaded...
let cell:myCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! myCollectionViewCell
// populate cells inside the collectionview with images
cell.imageView.image = UIImage(named: model[indexPath.item].imageName)
// set the currently selected cell (if one exists) to show its indicator styling
if(model[indexPath.item].selectedState == true){
cell.imageView.alpha = 1.0
} else {
cell.imageView.alpha = 0.5
}
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
// when a cell is tapped...
// reset all the selectedStates to false in the data model
for i in 0..<imagesArray.count {
model[i].selectedState = false
}
// set the selectedState for the tapped item to true in the data model
model[indexPath.item].selectedState = true
// refresh the collectionView (triggering cellForItemAtIndexPath above)
self.collectionView.reloadData()
}
but it does not persist if the user scrolls, because the cell is being re-used by the UI on some other level
Because you're doing it wrong. In didSelect, make no change to any cells. Instead, make a change to the underlying data model, and reload the collection view. It's all about your data model and your implementation of cellForItemAtIndexPath:; that is where cells and slots (item and section) meet.
Here's a simple example. We have just one section, so our model can be an array of model objects. I will assume 100 rows. Our model object consists of just an image name to go into this item, along with the knowledge of whether to fade this image view or not:
struct Model {
var imageName : String
var fade : Bool
}
var model = [Model]()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0..<100 {
// ... configure a Model object and append it to the array
}
}
override func collectionView(
collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return 100
}
Now, what should happen when an item is selected? I will assume single selection. So that item and no others should be marked for fading in our model. Then we reload the data:
override func collectionView(cv: UICollectionView,
didSelectItemAtIndexPath indexPath: NSIndexPath) {
for i in 0..<100 {model[i].fade = false}
model[indexPath.item].fade = true
cv.reloadData()
}
All the actual work is done in cellForItemAtIndexPath:. And that work is based on the model:
override func collectionView(cv: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let c = self.collectionView!.dequeueReusableCellWithReuseIdentifier(
"Cell", forIndexPath: indexPath) as! MyCell
let model = self.model[indexPath.item]
c.iv.image = UIImage(named:model.imageName)
c.iv.alpha = model.fade ? 0.5 : 1.0
return c
}
You logic is incorrect. didSelectItemAtIndexPath is used to trigger something when a cell is selected. All this function should contain is this:
let cell:stkCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! stkCollectionViewCell
cell.imageView.alpha = 1.0
selectedIndex = indexPath.item
Then in your cellForItemAtIndexPath function you should have the logic to set the cell because this is where the cells are reused. So this logic should be in there:
if (indexPath.item == selectedIndex){
print(selectedIndex)
cell.imageView.alpha = 1.0
}
else {
cell.imageView.alpha = 0.5
}