I am working on the iOS application with Swift 4. In that project I have requirement like, I have to create controls dynamically along with the proper alignment.
For example, I have a button when I click on that button I am hitting the service from that I am getting json data which contains 4 objects. Based on that I have to create controls dynamically and dynamic alignment also should do. I tried lot of examples and tutorials. I didn’t find any solution.
You can use UITableView for that and here is example:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tableview: UITableView!
var nameArr :[String] = []
override func viewDidLoad() {
super.viewDidLoad()
tableview.delegate = self
tableview.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func four_btn(_ sender: Any) {
nameArr.removeAll()
let nameData = ["First Name","Middle Name","Last Name","DOB"]
nameArr += nameData
tableview.reloadData()
}
#IBAction func eight_btn(_ sender: Any) {
nameArr.removeAll()
let nameData = ["Salutation","First Name","Middle Name","Last Name","DOB","Gender","Mobile","Email"]
nameArr += nameData
tableview.reloadData()
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return nameArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! tableviewCells
cell.nameLabel.text = nameArr[indexPath.row]
return cell
}
}
class tableviewCells : UITableViewCell{
#IBOutlet weak var nameLabel: UILabel!
}
You can use UITableView for the same
Your scenario is like, it may possible that one user having 5 records however another may have 10 or 12 records means you've to work dynamically
if there are 2 buttons which calls 2 different APIs then just manage 2 different array like this
var arr1 = NSArray()
var arr2 = NSArray()
var isAPI1Called = Bool()
save response of both apis in different array
then just manage flag on button tap and in suitable view like this
#IBAction func btn1(_ sender: Any) {
isAPI1Called = true
self.API1Called()
}
#IBAction func btn2(_ sender: Any) {
isAPI1Called = false
self.API1Called()
}
Now use flag in UITableview Delegate like this
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isAPI1Called
{
return arr1.count
}
else
{
return arr2.count
}
}
Load UITableviewCell as per your requirement if UI changed
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if isAPI1Called
{
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell1", for: indexPath) as! UITableviewCell
//Do your required stuff here
return cell
}
else
{
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell1", for: indexPath) as! UITableviewCell
//Do your required stuff here
return cell
}
}
Hope it will help you
Comment if not get any point
Related
My application fetches data from a mock API.
Using a custom cell, I display the names of authors on my landing page viewController.
When I click on a cell, it takes that author's book information to display on a 2nd TableViewController.
But even though the implementation is the same as for the landing page. My app freezes until I get a EXC_BAD_ACCESS error
It seems like it's stuck in an infinite loop, but without a proper error, it's hard to know why.
Infinite Loop?
I can get this to work without using a custom cell, but then I cannot display all the information I want (only book title or release date), so the data is there.
import UIKit
class BooksTableViewCell: UITableViewCell {
#IBOutlet weak var name: UILabel!
#IBOutlet weak var pages: UILabel!
#IBOutlet weak var release: UILabel!
// #IBOutlet var coverImage: UIImageView!
static let cellIdentifier = "BooksTableViewCell"
//
override func awakeFromNib() {
super.awakeFromNib()
}
static func nib() -> UINib {
return UINib(nibName: "BooksTableViewCell", bundle: nil)
}
//MARK: configure
public func configure(with viewModel: BooksCellViewModel) {
name.text = viewModel.name
pages.text = String(viewModel.pages)
release.text = viewModel.release
// coverImage.image = viewModel.image
}
}
import UIKit
class BooksTableViewController: UITableViewController {
var books: [Book] = []
var authorName: String = ""
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(BooksTableViewCell.nib(), forCellReuseIdentifier: BooksTableViewCell.cellIdentifier)
tableView.delegate = self
tableView.dataSource = self
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return authorName
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return books.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("Hello1")
let cell = tableView.dequeueReusableCell(withIdentifier: BooksTableViewCell.cellIdentifier, for: indexPath) as! BooksTableViewCell
print("Hello2")
let model = books[indexPath.row]
cell.configure(with: BooksCellViewModel(name: model.title, pages: model.pages, release: model.releaseDate))
return cell
}
}
The landing page controller and cell is similar but works with no problems
import UIKit
class LandingTableViewController: UITableViewController {
let parser = DataAPI()
var authors = [Author]()
var books = [Book]()
var authorName = ""
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(AuthorTableViewCell.nib(), forCellReuseIdentifier: AuthorTableViewCell.cellIdentifier)
tableView.delegate = self
tableView.dataSource = self
parser.getData {
data in
self.authors = data
//Reload UI on Main thread:
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "List of Authors"
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return authors.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: AuthorTableViewCell.cellIdentifier, for: indexPath) as! AuthorTableViewCell
let model = authors[indexPath.row]
cell.configure(with: AuthorCellViewModel(name: model.authorName))
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
books = authors[indexPath.row].books
authorName = authors[indexPath.row].authorName
performSegue(withIdentifier: "Show Books", sender: nil)
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
if (segue.identifier == "Show Books") {
let showBooksViewController: BooksTableViewController = segue.destination as! BooksTableViewController
showBooksViewController.books = books
showBooksViewController.authorName = authorName
}
}
}
I was able to fix the issue by correctly naming my variables. I needed to be using releaseDate not release as per my model object.
I have been at this for a few days now, what I am trying to do is to be able to select 5 cells (previous populated by a realm database). Any help on this would be really appreciated.
Thanks Mark
The following is my current code as is relates to the Segue to pass the data. I think I have edited the code down to the key elements. Let me know if you need any other information.
Edits Made - based on the suggestion made below. I took a step back and approached the problem differently, and developed a solution. I have updated the code below to reflect my working solution. Hope this helps someone else in the future. Cheers Mark
Primary VC
import UIKit
import RealmSwift
class ExercisesSelectionTimed: UITableViewController{
var realm = try! Realm()
var exercises : Results<ExercisesModel>?
#IBOutlet weak var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
//populates the Realm Data with the default list
exercises = realm.objects(ExercisesModel.self)
tableView.allowsMultipleSelection = true
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return exercises?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ExercisesAddedTableViewCell", for: indexPath) as! ExercisesAddedTableViewCell
// get the specific exercise in the array
let exercise = exercises?[indexPath.row]
cell.exercisenameLabel.text = exercise?.name
cell.equipmentnameLabel.text = exercise?.equipment
return cell
}
private func registerTableViewCells() {
let textFieldCell = UINib(nibName: "ExercisesAddedTableViewCell",bundle: nil)
self.tableview.register(textFieldCell,forCellReuseIdentifier: "ExercisesAddedTableViewCell")
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch (timerSelected) {
case "FGB":
//User needs to select 5 exercises to populate the FGB timer on the next VC
if let selectedRows = tableView.indexPathsForSelectedRows {
//lets the user select 5 cells
if selectedRows.count == 5 {
performSegue(withIdentifier: K.FGB, sender: self)
}
}
default : print ("error")
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch (timerSelected) {
case "FGB":
if let selectedRows = tableView.indexPathsForSelectedRows {
let selected = selectedRows.map{exercises?[$0.row]}
let destinationVC = segue.destination as! FBGTimerVCTable
destinationVC.selectedExercise = selected
}
default : print ("error")
}
}
}//last bracket in Class
The target for the data
import UIKit
import RealmSwift
class FBGTimerVCTable: HeartRateMonitorBrain{
var realm = try! Realm()
var selectedExercise = [ExercisesModel?]()
#IBOutlet weak var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
}//last bracket in Class
extension FBGTimerVCTable: UITableViewDataSource{
//setup tableview
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ExercisesAddedTableViewCell", for: indexPath) as! ExercisesAddedTableViewCell
//get the specific 5 exercises selected in the ExercisesSelectionTimed VC
cell.exercisenameLabel.text = selectedExercise[indexPath.row]?.name
cell.equipmentnameLabel.text = selectedExercise[indexPath.row]?.equipment
return cell
}
private func registerTableViewCells() {
let textFieldCell = UINib(nibName: "ExercisesAddedTableViewCell",bundle: nil)
self.tableview.register(textFieldCell,forCellReuseIdentifier: "ExercisesAddedTableViewCell")
}
}//last bracket in Class Extension
Realm
import RealmSwift
class ExercisesModel : Object {
#objc dynamic var name : String = ""
#objc dynamic var equipment : String = ""
}
so I want to make this simple wishlist feature for when the user tapped the "heart" button it will add that data from view to wishlist view. just like this :
so when the user tapped that heart button, that movie will show in this wishlist view like this :
now, my question is how do I notify my wishlistVc so that it knows there's a new "wishlist" that the user tapped from the movie list. I have an idea that I should use a delegate, but still, I can't figure out how to implement a delegate in this case.
and I use "var movieList" to store all the data in HomeVc, and my idea is when the user tapped that heart button in tableview, that data that user tapped with will move into my "let wishlist", so i can populate it on my wishlistVC ( but I don't know how to do this so I need help)
so far this is my code :
class DefaultTableViewCell: UITableViewCell {
#IBOutlet weak var moviePosterImage: UIImageView!
#IBOutlet weak var movieTitleLabel: UILabel!
#IBOutlet weak var wishlistButton: UIButton!
var indexPath: IndexPath!
var delegate: DefaultTableViewDelegate?
var wishlistFlag:Bool = false
override func layoutSubviews() {
super.layoutSubviews()
wishlistButton.titleLabel?.text = ""
wishlistButton.addTarget(self, action: #selector(wishlistTapped(_:)), for: .valueChanged)
}
#IBAction func wishlistTapped(_ sender: UIButton) {
wishlistFlag = !wishlistFlag
delegate?.wishlistTrigger(row: indexPath.row)
if wishlistFlag == true {
wishlistButton.setImage(UIImage(named: "heart_fill"), for: .normal)
}else if wishlistFlag == false {
wishlistButton.setImage(UIImage(named: "heart"), for: .normal)
}
}
}
HomeVc (the vc that shows the movie list):
var movieList : [Movie] = []
extension HomeVC: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return movieList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let data = movieList[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "DefaultTableViewCell", for: indexPath) as! DefaultTableViewCell
cell.indexPath = indexPath
cell.movieTitleLabel.text = data.title
cell.moviePosterImage.sd_setImage(with: data.imageUrl)
cell.delegate = self
return cell
}
}
protocol DefaultTableViewDelegate {
func wishlistTrigger(row: Int)
}
this is my wishlistVc:
let wishlist : [Movie] = []
extension WishlistVc: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return wishlist.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let data = wishlist[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "DefaultTableViewCell", for: indexPath) as! DefaultTableViewCell
cell.movieTitleLabel.text = data.title
cell.moviePosterImage.sd_setImage(with: data.imageUrl)
cell.wishlistButton.titleLabel?.text = ""
cell.indexPath = indexPath
return cell
}
}
I've been stuck for 2 whole days now I still don't know how to figure this out. I appreciate anyone that can help me. Thanks
Implement func like:
func wishlistTrigger(row: Int) {
self.myWishlistedItem.append(self.movieList[row]) //Add that wishlisted item in array
self.tableView.reloadData() //Now reload Table
}
I am trying to access each value of a text field in a prototype cell within a UITableView on Submit. I know I should be doing this in a better way (model) but for now, I just need to access these fields and cannot find a way to do this in Swift 3/4. Would anyone be able to assist?
Code:
import UIKit
import Firebase
class FormTableViewController: UITableViewController {
var formLabels = [String]()
var formPlaceholders = [String]()
override func viewDidLoad() {
super.viewDidLoad()
FirebaseApp.configure()
formLabels = ["Name","Email","Password", "Phone"]
formPlaceholders = ["John Smith","example#email.com","Enter Password", "8585551234"]
tableView.estimatedRowHeight = 30
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return formLabels.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier:
"FormTableCell", for: indexPath)
as! FormTableViewCell
let row = indexPath.row
cell.formLabel.font =
UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)
cell.formLabel.text = formLabels[row]
cell.formTextField.placeholder = formPlaceholders[row]
return cell
}
#IBAction func submitButtonPressed(_ sender: Any) {
// Need to do something with the Name, Email, Phone and Password fields here
}
}
You seem to acknowledge that updating the model directly probably makes sense. So why not do that? Just:
Have model collection for the responses;
Set up delegate for the text field in the cell;
Have cellForRowAt set that delegate; and
Make the table view controller conform to that class.
So, something quick and dirty, set up the cell to hook up editChanged event from the text field and set up protocol to inform the view controller:
protocol FormTableViewCellDelegate: class {
func fieldValueChanged(cell: UITableViewCell, textField: UITextField)
}
class FormTableViewCell: UITableViewCell {
weak var delegate: FormTableViewCellDelegate?
#IBOutlet weak var formLabel: UILabel!
#IBOutlet weak var formTextField: UITextField!
#IBAction func editingChanged(_ sender: UITextField) {
delegate?.fieldValueChanged(cell: self, textField: sender)
}
}
And then have the view controller set up model object and conform to your new protocol:
class FormTableViewController: UITableViewController {
var formLabels = [String]()
var formPlaceholders = [String]()
var values = [String?]()
override func viewDidLoad() {
super.viewDidLoad()
...
formLabels = ["Name","Email","Password", "Phone"]
formPlaceholders = ["John Smith","example#email.com","Enter Password", "8585551234"]
values = [nil, nil, nil, nil]
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FormTableCell", for: indexPath) as! FormTableViewCell
let row = indexPath.row
cell.formLabel.font = .preferredFont(forTextStyle: .headline)
cell.formLabel.text = formLabels[row]
cell.formTextField.placeholder = formPlaceholders[row]
cell.formTextField.text = values[row]
cell.delegate = self // set the delegate, too
return cell
}
#IBAction func submitButtonPressed(_ sender: Any) {
print(#function, values)
}
}
// delegate protocol to update model as text fields change
extension FormTableViewController: FormTableViewCellDelegate {
func fieldValueChanged(cell: UITableViewCell, textField: UITextField) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
values[indexPath.row] = textField.text
}
}
Then that's it, your model is updated as the text fields are updated. Plus this has the advantage that it now supports cell reuse, conforms to MVC patterns, etc.
If you want to just loop through cells, you can create an array of ‘IndexPath’.
let array = (0..<formLabels.count).map { IndexPath(row: $0, section:0) }
After that you can loop over this array and access individual cell using tableview method:- tableView.cellForIndexPath
Hope this helps. (Not on my laptop, so didn’t test the syntax)
I wrote an app that it have a UITableView in UIViewController and here is my codes:
class CategorySelectViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var melliSubCategories = [String]()
var mazhabiSubCategories = [String]()
var sayerSubCategories = [String]()
#IBOutlet weak var melliButton: UIButton!
#IBOutlet weak var sayerButton: UIButton!
#IBOutlet weak var mazhabiButton: UIButton!
#IBAction func melliButtonClicked(_ sender: UIButton) {
categorySelected = 6
melliButton.isHighlighted = true
mazhabiButton.isHighlighted = false
sayerButton.isHighlighted = false
categoryTableView.reloadData()
}
#IBAction func sayerButtonClicked(_ sender: UIButton) {
categorySelected = 5
melliButton.isHighlighted = false
mazhabiButton.isHighlighted = false
sayerButton.isHighlighted = true
categoryTableView.reloadData()
}
#IBAction func mazhabiButtonClicked(_ sender: UIButton) {
categorySelected = 4
melliButton.isHighlighted = false
mazhabiButton.isHighlighted = true
sayerButton.isHighlighted = false
categoryTableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
categoryTableView.dataSource = self
categoryTableView.delegate = self
categoryTableView.register(CategorySelectTableViewCell.self, forCellReuseIdentifier: "Cell")
melliSubCategories = DataBaseManager.shared.subCategories(6)
mazhabiSubCategories = DataBaseManager.shared.subCategories(4)
sayerSubCategories = DataBaseManager.shared.subCategories(5)
print(melliSubCategories)
print("/////////////////")
print(mazhabiSubCategories)
print("/////////////////")
print(sayerSubCategories)
print("/////////////////")
}
#IBOutlet weak var categoryTableView: UITableView!
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch categorySelected {
case 4: //mazhabi
return mazhabiSubCategories.count
case 5: //sayer
return sayerSubCategories.count
case 6: //melli
return melliSubCategories.count
default:
return melliSubCategories.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath as IndexPath) as! CategorySelectTableViewCell
cell.label?.text = melliSubCategories[indexPath.row]
return cell
}
And I create a class named CategorySelectTableViewCell for cells in table view that they have an image and a label.
In code I fill the arrays by database and I want to show them in the table view but the tableview doesn't show anything.
the screenshot : my storyboard, demo
You are saying:
cell.label?.text = melliSubCategories[indexPath.row]
It is impossible for this to work. For it to work, your custom cell type CategorySelectTableViewCell would need to be in a nib with a label outlet. But then that nib is either in a storyboard or a xib file. But you are also saying
categoryTableView.register(CategorySelectTableViewCell.self, ...
That line prevents the cell from coming from the xib or the storyboard. So the outlet cannot work and the cell will remain empty.
Please check array count is Zero or not in below numberOfRowsInSection method.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
Also, cross-check cell identifier is correct or not.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell : CategorySelectTableViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath as IndexPath) as! CategorySelectTableViewCell
cell.label?.text = melliSubCategories[indexPath.row]
return cell
}
Reload tableview in ViewDidLoad method:
categoryTableView.reloadData()
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view.
categoryTableView.dataSource = self
categoryTableView.delegate = self
categoryTableView.register(CategorySelectTableViewCell.self, forCellReuseIdentifier: "Cell")
melliSubCategories = DataBaseManager.shared.subCategories(6)
mazhabiSubCategories = DataBaseManager.shared.subCategories(4)
sayerSubCategories = DataBaseManager.shared.subCategories(5)
print(melliSubCategories)
print("/////////////////")
print(mazhabiSubCategories)
print("/////////////////")
print(sayerSubCategories)
print("/////////////////")
categoryTableView.reloadData()
}