I made a prototype cell and I have data from a database to load into the cells in a table.
Each cell has a label and 3 buttons just like this:
If I click on a button for example "Meets Standard", how can I identify in which row I tapped the button?
So for example when I press the "Meets Standard" button at a given row I'd like to change the background color of that row to red. How can I do it?
I have a CustomCell.swift class where I configure the prototype cell and a TableView.swift class where I configure the table.
Try control dragging the button to your CustomCell.swift class and typing a name and selecting "Action" on the popup menu. Then, inside the generated method, you can call self.backgroundColor = UIColor.redColor() or perform any other operations that you'd like.
Edit:
Here's what I think you should do to change the height:
Make a boolean in your cell class called "expanded" or something. Then, go into your table view class and implement the heightForRowAtIndexPath method. In that method, retrieve the cell and check if it's expanded, and if so, return a larger height. Now, to make it reload, you will need to store a reference to the table view in each cell, as it says here: Reference from UITableViewCell to parent UITableView?
In that clicked method you already made, where the background is set to red, you will need to call:
tableView.beginUpdates()
tableView.endUpdates()
where tableview is the weak reference you already stored. Also, in that method, you need to add self.expanded = true of course.
Edit 2:
You know what, maybe it's easier to just do this:
weak var _tableView: UITableView!
...
func tableView() -> UITableView! {
if _tableView != nil {
return _tableView
}
var view = self.superview
while view != nil && !(view?.isKindOfClass(UITableView))! {
view = view?.superview
}
self._tableView = view as! UITableView
return _tableView
}
Related
I have a TableView with a CollectionView inside.
So each Table cell is the delegate of the CollectionView.
The problem is that the collection View reloads on the main thread and the UITableView Cells are being reused.
That causes the collectionView cells to briefly present the labels of previous cells and then fade the changes.
How can I hide those labels when the tableView is initially passed through the return cell ?
The problem happens as collection.reload() is applied after the return cell of the UITableView in cell for row.
Here is a sample of the code.
UITableViewCell Code
let collectionDatasource = CollectionDataSource()
func configureCell(_ object: CustomClass) {
//Configure Object for tableView
self.presentedObject = object
self.nameLabel.text = object.name
//Set object and items on collection datasource
DispatchQueue.main.async {
let collectionItems = object.collectionItems ?? []
self.collectionDatasource.object = object
self.collectionDatasource.items = collectionItems
}
}
class CollectionDataSource: NSObject {
private var collectionView: UICollectionView?
var items: [CustomItem] = [] {
didSet {
DispatchQueue.main.async {
let changes = [CustomItem].changes(from: oldValue, to: self.items, identifiedBy: ==, comparedBy: <)
self.collectionView?.apply(changes: changes)
}
}
}
}
UICollectionView itself does its work asynchronously in contrast to UITableView. So, even if you will call reload collectionView directly in configureCell it will update the collection's cells after the table is set up (still can result in the mentioned issue). But you have added the "reload" call into DispatchQueue.main.async which makes the things worse.
You should skip using DispatchQueue.main.async if possible.
You should replace collectionView with a new one in configureCell. I suggest to add view into the cell of the same size and position as collection view and add collection view in the code. This will guarantee that previous collection view will never appear. 👍
I want to change my progress bar one by one in tableview. but when my use invisible cell in code, then it produces nil.
I use the tableview.cellforRowAt(IndexPath) in our code , Please resolve my problem
UITableViewCell is reusable, you cannot update UI of cells that are not displayed (visible), they just do not exist or being reused for another cell.
Reusable means that cell's view (UITableViewCell) will be used for any other cell that might just got in to the tableView bounds, this happens when you delete a row, or when you scroll away and the cell gets out of tableView bounds etc...
So, its not a problem, but by design.
BTW, you are using the correct method tableView.cellForRow(at: IndexPath), it will return a cell only if one is displayed.
You should instead update your data source and keep the progress value, when the cell next time being displayed it will show the value from data source, like so:
struct CellData {
var progress: Float
}
var dataSource: [CellData]
Now when you need to update the progress of cells, you can just update dataSource, like so
dataSource[index].progress = value
Then you call either call UITableView.realoadData() to refresh the UI of all cells that are visible, or you can update only that particular index, like so:
if let cell = self.tableView.cellForRow(at: IndexPath(row: index, section: 0)) as? CustomCell {
cell.progressView.setProgress(value, animated: true)
}
so here is how my setup looks like: Screenshot
As you can see I use TableViewsCells then I place ImageViews inside the cell. But as you can see on the screenshot whenever I hold my finger over the tableview it hightlights the whole cell area not the ImageView that acts as background ,so how can I make sure that it highlights the background Image only? Thanks
You can set the selectionStyle to .none in your UITableViewCell then override isSelected.
override var isSelected: Bool {
didSet {
// do whatever you want to your image here
}
}
I am making a table view app which retrieves the data from the Firebase. when making the user interface in the storyboard, I am using dummy image and label to visualize my app.
but when i run the app which consists of dynamic table view, those dummy images and label also shows up before immediately replaced by the actual data that i download from the Firebase storage.
can I set those images and labels to not show up when i run the app but still available in the storyboard?
thanks in advance :)
If they're just dummies, you can get rid of them when your view loads, before it appears onscreen:
override func viewDidLoad() -> Void{
super.viewDidLoad()
dummy.removeFromSuperview()
}
Whenever you want to hide/show an UIView:
myView.isHidden = true // hide
myView.isHidden = false // show
I assume what you need is to hide the views in viewWillAppear and then show them when necessary.
In your custom cell class, define something to hide the unwanted views:
func hideDummyViews() {
// do some stuff to hide what you don't want, e.g.
myEnclosingStackView.isHidden = true
}
In your table view controller, in the cellForRowAt indexPath func:
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
// Configure the cell ..
if yourDataSource[indexPath.row].isCompletelyLoaded {
// do your fancy dynamic cell layout
} else {
// show the basic version (minus any dummy views)
cell.hideDummyViews()
}
return cell
You can choose your preferred method for hiding the items (isHidden for each view, removing, adjusting constraints). I prefer to embed any disappearing views in a stack view and then use isHidden = true on the enclosing stack. This keeps things organized in your storyboard/XIB file and neatly recalculates constraints for the hidden stacks.
It seems that you want to show some empty (or incomplete) cells until database content arrives and then you will reload each cell as you process new entries in the datasource. This answer will initially give you a set of cells appearing as per your storyboard/XIB, minus the hidden dummy elements. Then as items in your datasource are loaded fully, you can reload the cells.
By the way, it seems like a lot of work to carefully layout these dummy views for "visualization" and then never show them in the app. Why not have some user-friendly place holders or progress indicators showing and then animate in the real/dynamic views as the data arrives?
I assume you download the Image from FireBase and does't want the dummy Image to appear
(Why dont you declare an empty ImageView and an empty Label!). Try setting an Image array in the viewController. Or you can use a struct array if you want a Image and label text together.
var arrImage=[UIImage]()
var arrLblTxt=[String]()
In view did load append your Demo Image's if required.
arrImage.append(UIImage("Demo"))
arrLblTxt.append("Demo")
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tbl_AssignEmployees.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as YourCell
if arrImage[IndexPath.row] != UIImage("Demo")
{
cell.ImageView.Image = arrImage[indexPath.row]
cell.textLabel!.text = arrLblTxt[indexPath.row]
}
else
{
cell.ImageView.Image = nil
cell.textLabel!.text = arrLblTxt[indexPath.row]
}
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsetsZero
cell.layoutMargins = UIEdgeInsetsZero
return cell
}
Then After you download your Image From FireBase,
Reset your Image array with the new Images downloaded from fireBase
Then reload your tableView. tableView.reloadData
I'm interested in creating a view that contains multiple features that users can scroll down and see i.e. pictures, description, comments, carousels etc. I am aware that the UICollectionView is able to provide this type of layout. I initially thought UITableViews would be the best approach.
I have looked at several tutorials and GitHub repos but majority of them just use a UICollectionView in a standard grid layout. I've also looked at IGListKit used by Instagram and some the tutorials linked to it.
I'm aiming to get something like the KitchenStories app:
I was wondering if someone could advice me in terms of the direction and approach best for this.
Don't try to do too much with any single view, even a UICollectionView.
The screen you've shown has a UITabBarController manage its top-level arrangement. The currently selected tab (“Home”) has a UINavigationController managing its content.
On the top of the navigation stack is, probably, a collection view or a table view. Either could be used here because the elements are visually laid out as screen-width rows in a stack. A table view is simpler because then you don't have to worry about setting up the layout.
The table view has several visible rows, each different:
The title/image row (“Easy seafood paella”)
The ratings row
The export row (hearts / save / share)
The comments row
The creator row (I assume, since it looks like it's probably a headshot and a name)
And there are probably even more unique rows out of view.
In your storyboard, you can design each of these rows as a prototype row in the table view controller's scene. Or you can design the table view with static content rows, which is easier if you won't need to change the order of the rows or duplicate any rows at run time.
“But Rob,” you say, “I can't fit all those rows into the table view in my storyboard!” Make the storyboard scene taller. UIKit will resize it at run time to fit the device screen.
In each row, drag in and arrange whatever subviews you need for that row's data. For example, the title/image row needs a UIImageView and a UILabel. The ratings row needs a label, probably a custom view to display and edit the stars, and perhaps a stack view for layout.
For each row, you'll need a separate subclass of UITableViewCell with outlets to that row's views. To pass the data to each cell for display, make each cell conform to a protocol:
protocol RecipeUsing {
var recipe: Recipe? { get set }
}
Then, in your table view controller, you set it like this if you're using static content:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = super.tableView(tableView, cellForRowAt: indexPath)
if let user = cell as? RecipeUsing {
user.recipe = recipe
}
return cell
}
You'll need a RecipeTitleImageCell with outlets to the UIImageView and UILabel. Something like this:
class RecipeTitleImageCell: UITableViewCell, RecipeUsing {
#IBOutlet var label: UILabel!
// UITableViewCell has an imageView property that's not an outlet 😭
#IBOutlet var myImageView: UIImageView!
var recipe: Recipe? {
didSet {
guard let recipe = recipe else { return }
label.text = recipe.title
myImageView.image = recipe.image
}
}
}
And for the ratings row, you'll want something like this:
class RecipeRatingsCell: UITableViewCell, RecipeUsing {
#IBOutlet var ratingControl: RatingControl!
#IBOutlet var label: UILabel!
var recipe: Recipe? {
didSet {
guard let recipe = recipe else { return }
ratingControl.rating = recipe.ratings.reduce(0, +) / recipe.Double(ratings.count)
if ratings.count < 5 { label.text = "Too few ratings" }
else { label.text = "\(ratings.count) ratings" }
}
}
}