Problem with TableviewController/Rows/Images - ios

Im Developing an App and I have some few problems with my tableviewcontroller.First of all, left to the picture there is free space.How can I get the Image bigger ? or how can I get this so that there is no free space. The image is a cell.imageview and its getting his image data from core data.
And the next problem is. I have some problems with the rows, you can see this on the picture.How can I fix this ?

It seems that you use the default UITableViewCell class , but you need to create a subclass of it and make every content custom
regrading label trimming you need to set .numberOfLines property of the UILabel item to 0 so it can wrap , a review for constraints may be like this
ImageView : width , height (static/propertional) , top , leading
label : leading to imageView , top - trailing and bottom to cell
This Using Auto Layout in UITableView for dynamic cell layouts & variable row heights may help you in creating the dynamic table cell

First of all, it would be much more constructive for you to implement a subclass of UITableViewCell and specify any cell-related logic in there. An intuitive approach - based on the contents of your table view screenshot - would look something like this
class ShowTableViewCell: UITableViewCell {
// MARK: - Outlets
#IBOutlet weak var imgThumbnail: UIImageView!
#IBOutlet weak var lblTitle: UILabel!
// MARK: - Functions
func setUp(withShow show: Show) {
self.imgThumbnail.image = show.thumbnailImage
self.lblTitle.text = show.title
}
}
Note that the setUp(withShow:) function declared above is using a user-defined object of type Show
class Show {
// MARK: - Properties
var title: String?
var thumbnailImage: UIImage?
// MARK: - Initializers
public init(withTitle title: String, andImage image: UIImage) {
self.title = title
self.thumbnailImage = image
}
}
In my opinion, the latter approach conforms much better with Object Oriented Programming (OOP) standards. In case you need to add/remove any property to your Show object, it will be so much easier and cleaner to reflect those changes within your cells. All you would have to do is add/remove the respective #IBOutlet objects from your cell and configure them just as demonstrated in the setUp(withShow:) class function.
Moving on, you implement the following view controller that conforms to the UITableViewDataSource protocol, and populate your table accordingly
class ViewController: UIViewController {
// MARK: - Properties
var shows = [
Show(withTitle: "UFC 127: Penn vs. Fitch", andImage: RESPECTIVE_IMAGE),
Show(withTitle: "Strangers in Good Company", andImage: RESPECTIVE_IMAGE),
Show(withTitle: "Candles on Bay Street", andImage: RESPECTIVE_IMAGE),
Show(withTitle: "Flight Angels", andImage: RESPECTIVE_IMAGE)
]
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
}
}
// MARK: - UITableViewDataSource Extension
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return shows.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "showCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! ShowTableViewCell
cell.setUp(withShow: shows[indexPath.row])
return cell
}
}
Note that you when declaring the shows array, you should replace the RESPECTIVE_IMAGE placeholder I have added with whatever image you want. You should also populate it with whatever static elements you want - or more dynamically - query them from your database.
Additionally, using auto-layout, you can manually determine the size of your image and, using the Number of Lines label attribute, you can prevent your labels from trimming.

Related

Swap and reload data in tableView for different Realm object

Problem I want to allow users to hit 'swap' in a table cell and then find a different Realm object to populate the 2 text labels (for exercise name and number of reps) in the cell with the values from the new object.
Research There's quite a bit (admittedly old) on 'moving rows' (e.g. here How to swap two custom cells with one another in tableview?) and also here (UITableView swap cells) and then there's obviously a lot on reloading data in itself but I can't find anything on this use case.
What have I tried my code below works fine for retrieving a new object. i.e. there's some data in the cell, then when you hit the 'swapButton' it goes grabs another one ready to put in the tableView. I know how to reload data generally but not within one particular cell in situ (the cell that the particular swap button belongs to... each cell has a 'swap button').
I'm guessing I need to somehow find the indexRow of the 'swapButton' and then access the cell properties of that particular cell but not sure where to start (I've played around with quite a few different variants but I'm just guessing so it's not working!)
class WorkoutCell : UITableViewCell {
#IBOutlet weak var exerciseName: UILabel!
#IBOutlet weak var repsNumber: UILabel!
#IBAction func swapButtonPressed(_ sender: Any) {
swapExercise()
}
func swapExercise() {
let realmExercisePool = realm.objects(ExerciseGeneratorObject.self)
func generateExercise() -> WorkoutExercise {
let index = Int(arc4random_uniform(UInt32(realmExercisePool.count)))
return realmExercisePool[index].generateExercise()
}
}
//do something here like cell.workoutName
//= swapExercise[indexRow].generateExercise().name???
}
Hold your objects somewhere in a VC that shows UITableView. Then add the VC as the target to swap button. Implement swapping objects on button press and reload data of table view after.
The whole idea is to move logic to view controller, not in separate cell.
There are 2 ways.
1. Adding VS as button action target.
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ... // get cell and configure it
cell.swapBtn.addTarget(self, action: #selector(swapTapped(_:)), for: .touchUpInside)
return cell
}
func swapTapped(_ button: UIButton) {
let buttonPosition = button.convertPoint(CGPointZero, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(buttonPosition)!
// find object at that index path
// swap it with another
self.tableView.reloadData()
}
Make VC to be delegate of cell. More code. Here you create protocol in cell and add delegate variable. Then when you create cell you assign to VC as delegate for cell:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ... // get cell and configure it
cell.delegate = self
return cell
}
func swapTappedForCell(_ cell: SwapCell) {
// the same logic for swapping
}
Solution from OP I adapted the code here How to access the content of a custom cell in swift using button tag?
Using delegates and protocols is the most sustainable way to achieve this I think.
I hope this helps others with the same problem!

Multiple charts in same view IOS Charts

I have been trying to develop a view controller with multiple charts(bar chart, line chart, pie chart). I created a table view and custom table view cell. There is a UIView inside custom table view cell. However, when I am trying to cast that UIView to BarchartView it gives me an error
Could not cast value of type 'UIView' (0x10a8e7f40) to 'Charts.LineChartView' (0x1074f63a0).
How can I achieve that multiple charts in same table view? Thanks in advance.
cellForRowAt indexPath:
let cell = myTableView.dequeueReusableCell(withIdentifier: "chart") as? GraphicCell
var lineChart:LineChartView = cell?.chart as! LineChartView
lineChart.noDataText = "A"
return cell!
the view outlet that I have created in GraphicCell is UIView type
The charts which are shown depends on the user choice. User can select one bar chart and two line chart or only two line chart without bar chart. I do not understand completely how to achieve this. I have added my project to GitHub project link
You need to create prototype cells for each of the types of charts, which you want to use in your TableView. In each prototype cell you need to put UIView and then change the class of UIView to LineChartView, BarChartView, etc.
Also you need to define your own class for each prototype cell, e.g:
class MyLineChartCell: UITableViewCell {
#IBOutlet weak var lineChartView: LineChartView!
func configure (/* ... */) {
lineChartView.noDataText = "You have no data"
// ...
}
}
.
Use this classes for you prototype cells:
Then in func func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell you could choose which prototype will be used at the moment.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyLineChartCellIdentifier") as! MyLineChartCell
cell.configure(/* Data, Colors, etc. */)
}
if indexPath.row == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyBarChartCellIdentifier") as! MyBarChartCell
cell.configure(/* Data, Colors, etc. */)
}
//... some other types of cells
return cell
}
The chartView can´t be of type UIView, it has to have the correct type when you declare it. You can have 3 different views inside the tableView cell, like this:
#IBOutlet weak var barChart: BarChartView!
#IBOutlet weak var pieChart: PieChartView!
#IBOutlet weak var lineChart: LineChartView!
and then use the one you need, depending on which graph type you want. If you are using Storyboard, you also need to choose class for each view as BarChartView, LineChartView or PieChartView in the Storyboard.

Content of a cell in static tableview isn't shown SWIFT 3

Here is my implementation of tableView(_:cellForRowAt:):
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let index = indexPath.section
let weekDay = WeekDays.day(at: index)
if self.availability.numberOfTimeslots(for: weekDay) == 0 {
let cell = NotSelectedCell(style: .default, reuseIdentifier: nil)
return cell
}
return UITableViewCell()
}
Here is my code for my custom table view cell:
class NotSelectedCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.backgroundColor = .red
self.textLabel?.numberOfLines = 0
self.textLabel?.textAlignment = .center;
self.textLabel?.text = "Not Available"
}
}
I've also tried initializing custom cell cell = NotSelectedCell() the result is the same. The content isn't shown. dataSource or viewDelegate aren't the problem as I'm working with UITableViewController.
Here's an image
The problem is awakeFromNIB "prepares the receiver for service after it has been loaded from an Interface Builder archive, or nib file." But you're instantiating this programmatically, so that method isn't called. You could theoretically move the code to init(style:reuseIdentifier:), make sure to call super in your implementation, and do any additional customization after that point.
But, you generally wouldn't programmatically instantiate cells when using static cells. (It's the point of static cells, that IB takes care of everything for you.) You generally don't implement UITableViewDataSource at all when using static cells.
I would advise using dynamic table and have two cell prototypes, one with reuse identifier of "NotAvailable" and one with "Available" (or whatever identifiers you want). Then programmatically instantiate the cell with the appropriate identifier. (By the way, this also has the virtue that your cell with "NotAvailable" can be designed entirely in IB, and no code is needed, for that cell at least.) This way, the storyboard takes care of instantiating the appropriate cell.
So, here I have two cell prototypes in my dynamic table, one for "not available" and one for "available":
Then the code would look at the model to figure out which to instantiate:
// for the complicated cell where I want to show details of some window of availability, add IBOutlets for that cell's labels
class AvailableCell: UITableViewCell {
#IBOutlet weak var startLabel: UILabel!
#IBOutlet weak var stopLabel: UILabel!
#IBOutlet weak var doctorLabel: UILabel!
}
// some super simple model to represent some window of availability with a particular doctor in that office
struct Availability {
let start: String
let stop: String
let doctor: String
}
class ViewController: UITableViewController {
let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
let available = ...
override func numberOfSections(in tableView: UITableView) -> Int {
return days.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return available[days[section]]?.count ?? 1
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return days[section]
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// see if there are any available windows for the given day, if not, return "not available" cell
guard let availabilities = available[days[indexPath.section]] else {
return tableView.dequeueReusableCell(withIdentifier: "NotAvailable", for: indexPath)
}
// otherwise, proceed with the more complicated "Available" cell where I have to populate various labels and the like
let cell = tableView.dequeueReusableCell(withIdentifier: "Available", for: indexPath) as! AvailableCell
let availability = availabilities[indexPath.row]
cell.startLabel.text = availability.start
cell.stopLabel.text = availability.stop
cell.doctorLabel.text = availability.doctor
return cell
}
}
And that would yield:
Now, clearly, I just whipped up a super primitive model, and didn't do any UI design in the "available" cell prototype other than inserting three labels. But it illustrates the idea: If your dynamic table has multiple unique cell designs, just implement cell prototypes for each with unique identifiers and instantiate the appropriate one. And this way, you enjoy full cell reuse, minimize how much visual design you have to do programmatically, etc.
You are not supposed to use the cellForRow:atIndexPath method when using static cells. The cells are static, so the loading flow is different. What i'd suggest is to connect the cells individually from the interface builder to your view controller.
STILL, if you want to do it this way you have to get your cells by calling "super" since that's the class who is actually generating your static cells.
UITableView with static cells without cellForRowAtIndexPath. How to set clear background?
EDIT:
I just noticed that this is wrong:
if self.availability.numberOfTimeslots(for: weekDay) == 0 {
let cell = NotSelectedCell(style: .default, reuseIdentifier: nil)
return cell
}
You have to use the "dequeueReusable" method or something. Then again, these are STATIC Cells, so you should just be linking the cells directly from the interface builder.

Trying to create a custom UITableView and cell for for some reason table is showing bars

I'm trying to create a custom UITableView for some reason the table is showing the bars below. Does anyone know how to get rid of those bars?
Here's what my code for my TableViewController looks like as well.
//
// TableTableViewController.swift
// CarApp
//
// Created by Lillybridge, Kevin on 2/10/17.
// Copyright © 2017 RS Design Lab. All rights reserved.
//
import UIKit
struct cellData {
let cell : Int!
let text : String!
let image : UIImage!
}
class TableTableViewController: UITableViewController {
var arrayOfCellData = [cellData]()
override func viewDidLoad() {
super.viewDidLoad()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("TableViewCell", owner: self, options: nil)?.first as! TableViewCell
print("how many times is this being called")
return cell
}
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.separatorStyle = .none //gets rid of separator lines
tableView.rowHeight = 75 //the height of the nib view
}
U need to setup the cell height. Otherwise better solution is drag and drop a UIView on the header of TableView thats it.
There should be no reason the image takes up the size that it does. Cell height will not help because it is taking up not one cell, but many. It seems like you are adding the image to the view itself, rather than the cell.
As a solution,
Make sure that an individual cell has the image added t it, rather than the view
Once that is done, set up constraints on the image so that is is bound to the dimensions of its parent cell.
Make sure you have set the cell height using tableview.rowHeight or even tableview.estimatedRowHeight.
I think I figured it out. Changing the "separator" in the inspector seemed to do the trick. I will try setting the height though as well since I want to add more cells. Thanks!

How To Bind An Array Of Custom Objects Using Swift 3, ReativeKit, and Bond

Background
I have an IOS application that can receive a real-time stream of data. I have implemented custom value objects to store/capture this data from a live stream. I now need to bind my custom data objects to the UI (mostly use tableviews and custom cell's that call into those custom object values).
Question
How can I bind an array of custom object values to my UI using Bond, ReactiveKit, or other framework in Swift 3?
Example Code
public class Device {
var name: String
var status: String
}
public class DeviceController {
var devices = Array<Device>()
// more code to init/populate array of custom Device classes
}
public class CustomViewController: ... {
var deviceController = DeviceController()
var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// more code to register custom cell
}
public class CustomCell:UITableviewCell {
#IBOutlet weak var deviceName: UILabel!
#IBOutlet weak var deviceStatus: UILabel!
}
You use the delegate pattern, which is already set up for many UIKit elements including UITableView.
A UITableView has two properties that can be any object conforming to two protocols, specifically
var dataSource: UITableViewDataSource?
var delegate: UITableViewDelegate?
So for your UITableView, you assign an object to act as the dataSource and delegate. Often, but not always, one would make the containing ViewController both the dataSource and delegate.
override func viewDidLoad() {
tableView.dataSource = self
tableView.delegate = self
...
}
However, you first have to make the ViewController conform to those protocols.
public class CustomViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
Command click on the two conformance declarations and you'll be able to see the methods you have to add to your view controller to conform. They're pretty obvious what they do, and you'll probably be able to figure it out from there.
But specifically you need to add the numberOfRows, numberOfSections and this method, which is I think the one you're asking about.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// dequeue a cell for reuse
// type cast the cell to your UITableViewCell subclass
// set the device name and device status labels like so..
cell.deviceName = deviceController.devices[indexPath.row].name
cell.deviceStatus = deviceController.devices[indexPath.row].status
return cell
}
From there, the tableView will automatically request the data when the subviews are laid out. If your data isn't all available instantly, you can call tableView.reloadData() when it is.

Resources