I used CollectionView inside TableViewCell. All works fine and shown all as expected. But if I scrolled the TableView very fast, items (i used images in collectionView) from one collection replaced with items (images) from another collection and override it on View (on Debug mode in code al works fine, its just displaying of them).
UITableView GetCell():
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var item = _view.Items[indexPath.Row];
var cell = (MyTableCell)tableView.DequeueReusableCell(“cell”);
cell.TextLabelView.Text = item.Title;
cell.YesButtonView.Hidden = item.IsCategory;
cell.NoButtonView.Hidden = item.IsCategory;
if (item.IsImagePoint)
{
cell.ImagesCollectionView.DataSource = new ItemsDataSource(item.Images, cell.ImagesCollectionView);
cell.ImagesCollectionView.Delegate = new ItemsDelegate(item, _view);
}
return cell;
}
UICollectionView GetCell():
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
var cell = (ImageViewCell)_collectionView.DequeueReusableCell(new NSString(“ImageViewCell”), indexPath);
var image = _images[indexPath.Row];
var imagePath = image.ThumbnailPath;
if (!string.IsNullOrEmpty(imagePath))
{
cell.ImagePath = imagePath;
}
return cell;
}
Swift 5
Add this in your custom UITableViewCell class.
override func prepareForReuse() {
collectionView.dataSource = nil
collectionView.delegate = nil
collectionView.reloadData()
collectionView.dataSource = self
collectionView.delegate = self
}
It's probably because of the reuse system of cells in UITableView. Do you set up properly your data when you configure the cell? Do you call CollectionView's reloadData()?
EDIT: You should call it in the tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell where you configure your cell. This way, each time a cell is reused, you update its content.
EDIT 2: Just like I said try to add the collection view reloadData() when you set up your tableview cell. You also have to clean your datasource and delegate, because it's a reused cell so it may already have been used with another value.
if (item.IsImagePoint)
{
cell.ImagesCollectionView.DataSource = new ItemsDataSource(item.Images, cell.ImagesCollectionView);
cell.ImagesCollectionView.Delegate = new ItemsDelegate(item, _view);
}
else
{
cell.ImagesCollectionView.DataSource = null;
cell.ImagesCollectionView.Delegate = null;
}
cell.ImagesCollectionView.ReloadData()
return cell;
Related
Code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
registerCells()
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifierForItem(at: indexPath.row), for: indexPath) as? HorizontalCollectionTableViewCell else {
return UITableViewCell()
}
if cellViewModels.count > indexPath.row {
let viewModel = cellViewModels[indexPath.row]
cell.viewModel = viewModel
}
return cell
}
Passing viewModel to Cell:
var viewModel: TitleAccessoryButtonCollectionViewModel? {
didSet {
guard let viewModel = viewModel else {
return
}
titleLabel.text = viewModel.title
if let buttonTitle = viewModel.accessoryButtonModel?.title {
setAccessoryButtonTitle(buttonTitle)
}else{
accessoryButton.hideTitleLabel()
}
if let buttonImage = viewModel.accessoryButtonModel?.image {
accessoryButton.buttonImageView.image = buttonImage
}
else {
accessoryButton.hideImageView()
}
sectionContentImage.image = viewModel.sectionContentImage
titleLabelLeadingConstraint.constant = viewModel.titleLabelLeadingSpacing
accessoryButton.isHidden = viewModel.hideAccessoryButton
sectionContentView.isHidden = viewModel.hidePremiumContentView
let collectionViewModel = viewModel.collectionViewModel
collectionViewHeight.constant = CGFloat(collectionViewModel.height)
collectionViewModel.setup(collectionView: collectionView)
collectionView.delegate = collectionViewModel.delegate
collectionView.dataSource = collectionViewModel.dataSource
collectionView.reloadData()
}
}
Description:
I have six UITableViewCell mostly, and they are reusable.
In every UITableViewCell is UICollectionView.
Five UICollectionView's use normal UICollectionViewFlowLayout's, but one needs a custom subclass.
The problem is that when UITableViewCell with custom UICollectionViewFlowLayout is hiding and new UITableViewCell is showing and cell with this custom flow layout is reused and UICollectionView already have UICollectionViewFlowLayout but is bad.
Is any nice way to clear this layout or prevent this situation?
Maybe something with prepareForReuse()?
I add that UICollectionView is outlet in UITableViewCell.
This excellent article helped me a lot to get UICollectionViews in UITableviewCells up and running: https://ashfurrow.com/blog/putting-a-uicollectionview-in-a-uitableviewcell-in-swift/
To update the layout you can call
collectionView.collectionViewLayout.invalidateLayout()
See also:
Swift: How to refresh UICollectionView layout after rotation of the device
I'm trying to set a different style for my active object in a TableView. I tried setting a flag for my object (myObject.isActive) and read it in my custom UITableViewCell like this;
var myArray = [MyObject]()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "myCustomCell", for: indexPath) as? myCustomCell {
if myArray.count > 0 {
// Return the cell
let myObject = myArray[indexPath.row]
cell.updateUI(myObject: myObject)
return cell
}
}
return UITableViewCell()
}
myCustomCell:
func updateUI(myObject: MyObject){
if myObject.isActive {
self.selectedCell()
}
}
func selectedCell() {
labelTitle.font = UIFont(name: "Montserrat-Medium", size: 32)
labelTitle.textColor = UIColor(hex: 0x64BA00)
}
This works great when the tableView data loads. But when I scroll the tableView other cells are also styling differently. How can I solve this?
Cells get reused. You need to handle all possibilities. Your updateUI method changes the cell if myObject is active but you make no attempt to reset the cell if it isn't.
You need something like:
func updateUI(myObject: MyObject){
if myObject.isActive {
selectedCell()
} else {
resetCell()
}
}
And add:
func resetCell() {
// Set the cell's UI as needed
}
Another options is to override the prepareForReuse method of the table cell class. That method should reset the cell to its initial state.
I want to add a footer view to a UITableView that shows a UIProgresIndicator when the user has reached the end of the list and new data will be loaded, or a UILabel when there are no more data to be fetched.
I have used the code below, but nothing happens:
UITableViewHeaderFooterView footer = new UITableViewHeaderFooterView ();
UILabel futerlabel = new UILabel ();
futerlabel.Text = "Duke u ngarkuar";
footer.AddSubview (futerlabel);
Any way how to achieve this.
If you are in a UITableViewController try this:
TableView.TableFooterView = footer;
UPDATE
After having a think about this I would suggest not using a footer but rather an extra cell at the end of your data, this will get this effect.
Using this method to check if you have scrolled to the last item and to update the table's data:
public override void WillDisplay (UITableView tableView, UITableViewCell cell, NSIndexPath indexPath)
{
// if showing last row of last section, load more
if (indexPath.Section == tableView.NumberOfSections()-1 && indexPath.Row == tableView.DataSource.RowsInSection(tableView, indexPath.Section)-1 && !FullyLoaded) {
var growRowSource = tableView.DataSource as GrowRowTableDataSource;
if (growRowSource != null) {
FullyLoaded = growRowSource.LoadNextPage ();
Task.Delay (5000);
tableView.ReloadData ();
}
}
}
And then in the Delegate checking for the last item and creating a different cell like so:
public override UITableViewCell GetCell (UITableView tableView, Foundation.NSIndexPath indexPath)
{
if (indexPath.Row == LoadedItems.Count) {
var loadingCell = tableView.DequeueReusableCell (LoadingCellID, indexPath) as LoadFooterCell;
loadingCell.Loading = !hasNextPage;
return loadingCell;
}
var cell = tableView.DequeueReusableCell (CellID, indexPath) as GrowRowTableCell;
var item = LoadedItems [indexPath.Row];
// Setup
cell.Image = UIImage.FromFile(item.ImageName);
cell.Title = item.Title;
cell.Description = item.Description;
return cell;
}
bool hasNextPage;
I quickly mocked an example from the GrowTable Xamarin sample code here:
https://github.com/b099l3/LoadingTableExample
You could accomplish this in several ways, I will layout a few here:
In your TableViewSource you can do this:
1.) Implement: public override UIView GetViewForFooter(UITableView tableView, nint section) and return your UILabel
2.) Implement public override nfloat GetHeightForFooter(UITableView tableView, nint section) and return a height for your UIView
If your Footer is going to be a simple UILabel, you can replace Step 1 above with this:
Implement public override string TitleForFooter(UITableView tableView, nint section) and return "Duke u ngarkuar".
You will still need to implement public override nfloat GetHeightForFooter(UITableView tableView, nint section) and return a height otherwise your footer wont show up.
Alternatively, UITableView exposes a property called TableFooterViewthat allows you to set a UIView for a footer.
You can size this to your liking.
var footerView = new UIView(new RectangleF(0, 0, 375, 66));
TableView.TableFooterView = footerView;
I have tried to make a UICollectionViewController where I can show a image for each cell. When I want to open this ViewController it shows me an error
import UIKit
private let reuseIdentifier = "Cell"
class RodelCollectionViewController: UICollectionViewController {
var personService: PersonService!
override func viewDidLoad() {
super.viewDidLoad()
assert(personService != nil, "Person Service has to be set, otherwise this class can't do anything useful.")
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Register cell classes
self.collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return personService.allPersons().count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("PersonCollectionCell", forIndexPath: indexPath)
if let rodelCollectionViewCell = cell as? RodelCollectionViewCell {
rodelCollectionViewCell.personView?.person = personService.allPersons()[indexPath.item]
}
return cell
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let PersonDetailViewController = segue.destinationViewController as? PersonDetailViewController,
let person = (sender as? RodelCollectionViewCell)?.personView?.person {
PersonDetailViewController.person = person
}
}
This is the error
I have tried a lot to fix it but it allways shows me the same error. I don't know where I have to solve this
Did you assign the cell identifier ("PersonCollectionCell") to the cell in the xib file or in the storyboard?
I noticed you declared private let reuseIdentifier = "Cell" that you use to register the cell. But you are using a different reuseIdentifier "PersonCollectionCell" when dequeuing the cell.
Also,
I wouldn't recommend using a function personService.allPersons() inside:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
This method gets called every time a cell will be reuse/dequeued and could bring performance issues in the future. Instead I would save the result inside an array and update it every time something change and can affect what personService.allPersons() returns.
I would declared a lazy variable like this:
private lazy var allPersons: [WhateverTheTypeIs] = {
let allPersons = self.personService.allPersons()
return allPersons
}
and in the collectionView datasource methods use allPersons instead of the method itself.
Hope this helps.
Another problem which is found with your code is in the
self.collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
Here you are trying to register a default UICollectionViewCell and in the cellForItemAtIndexPath you are trying to check for the
if let rodelCollectionViewCell = cell as? RodelCollectionViewCell {
rodelCollectionViewCell.personView?.person = personService.allPersons()[indexPath.item]
}
Here in this code you are checking for your custom cell how this cell become custom cell
if you want to register and create your custom cell your should be like this:
At viewDidLoad()
self.collectionView!.registerClass(RodelCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
At cellForItemAtIndexPath
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! RodelCollectionViewCell
Default cell
If you want to keep the default cell your code will remain same as it's currently but it will not go inside the condition of custom cell the cell may be show empty if you don't do anything else in the cellforrow
Update
Put both of the code in the cellForItemAtIndexPath
To change cell background color
cell.contentView.backgroundColor = UIColor.redColor()
As person view is coming nil for now as testing purpose we can add a sample view
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("PersonCollectionCell", forIndexPath: indexPath)
if let rodelCollectionViewCell = cell as? RodelCollectionViewCell {
rodelCollectionViewCell.personView?.person = personService.allPersons()[indexPath.row]
}
cell.contentView.backgroundColor = UIColor.redColor()
let lbl = UILabel(frame:CGRectMake(0,0,100,21))
lbl.text = "\(indexPath.row)" //replace this value with your original value if it displays for the first time
cell.contentView.addSubview(lbl)
return cell
}
I have a table view and I am adding several cells to it based on my json.
So far the code for adding cells looks as follows:
override func viewWillAppear(animated: Bool) {
let frame:CGRect = CGRect(x: 0, y: 90, width: self.view.frame.width, height: self.view.frame.height-90)
self.tableView = UITableView(frame: frame)
self.tableView?.dataSource = self
self.tableView?.delegate = self
self.view.addSubview(self.tableView!)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("CELL")
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Value1, reuseIdentifier: "CELL")
}
let user:JSON = JSON(self.items[indexPath.row])
cell!.textLabel?.text = user["description"].string
var photoURL = "/path/to/my/icon/google.png"
if let data = NSData(contentsOfFile: photoURL)
{
cell!.imageView?.image = UIImage(data: data)
}
return cell!
}
Besides the description in my json I have also username and price. So far - since I'm adding only imageView and description, 3 cells look like this:
Is there a way to style it so that each cell looks similar to this:
(price and username are grey here`)? How can I achieve this effect?
===EDIT:
this is how I populate my table:
I'm fetching data from rest webservice to json:
func getAllUsers() {
RestApiManager.sharedInstance.getUsers { json in
let results = json
for (index: String, subJson: JSON) in results {
let user: AnyObject = JSON.object
self.items.addObject(user)
dispatch_async(dispatch_get_main_queue(),{
self.tableView?.reloadData()
})
}
}
}
and I invoke this method in my viewWillAppear function
You can make your table use custom UITableViewCells and style them to your liking.
In a nutshell, you create a prototype cell in Storyboard that looks like the example you posted and connect it to a custom UITableViewCell class with the elements you created. At cellForRowInIndexPath you return your custom cell rather than regular UITableViewCells.
Check out this tutorial for details: http://shrikar.com/uitableview-and-uitableviewcell-customization-in-swift/
Create the layout of the cell using a custom style. Place labels and imageView like you would anywhere else in storyborad.
You will need to create a UITableViewCell file. The one I used is named ExampleTableViewCell. Make note of the subclass.
Now connect your cell to the ExampleTableViewCell you just created.
Now we can make outlets from the labels and imageView of the cell into the ExampleTableViewCell. Control drag from each element into the ExampleTableViewCell.
The final step is to configure the cell using the cellForRowAtIndexPath func. Make note of the var cell. We now cast this to the ExampleTableViewCell. Once we do this we can use the outlets in the ExampleTableViewCell to set our labels and image. Make sure you set the resuseIdentifier for the cell in the storyboard. If you are unfamiliar with this leave a comment and I can add instructions for this.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier") as! ExampleTableViewCell
cell.imageDisplay.image = yourImage
cell.descriptionLabel.text = yourDescription
cell.priceLabel.text = yourPrice
cell.usernameLabel.text = yourUsername
return cell
}
Subclass UITableViewCell. You can go to the TableView on your storyboard and go to one of the prototypes and set it's class to your custom class and it's style to Custom and then you can ctrl+click & drag outlets/actions to the UITableViewCell subclass the same way you would for a basic view controller.