I'm tired of searching about what I want, so I will ask here and hope you guys help if possible.
I have a tableview contain segments in each cell and I want to save all cells segment using button [outside the tableview] so I can show them in another table later.
Here is my tableview
here is my view Controller:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
let arr = ["item 1",
"item 2",
"item 3"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CustomCell.self, forCellReuseIdentifier: "cell")
}
#IBAction func saveButtonAction(_ sender: UIButton) {
// I want to save the cells segmented control selectedSegmentIndex???
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
90
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
cell.textView.text = arr[indexPath.row]
return cell
}
}
and here is my Custom Cell and it contain TextView and Segment Controller I tried to save the segments changes in array but I don't know what to do after lol:
import UIKit
class CustomCell: UITableViewCell {
let textView: UITextView = {
let tv = UITextView()
tv.translatesAutoresizingMaskIntoConstraints = false
tv.font = UIFont.systemFont(ofSize: 16)
tv.isEditable = false
return tv
}()
// var rowIndexPath: Int?
var segmentArray: [Int] = []
lazy var itemSegmentedControl: UISegmentedControl = {
let sc = UISegmentedControl(items: ["1", "2", "3"])
sc.translatesAutoresizingMaskIntoConstraints = false
sc.tintColor = UIColor.white
sc.selectedSegmentIndex = 0
sc.addTarget(self, action: #selector(handlePointChange), for: .valueChanged)
return sc
}()
let stackView: UIStackView = {
let sv = UIStackView()
sv.translatesAutoresizingMaskIntoConstraints = false
return sv
}()
#objc func handlePointChange() {
let segChange = itemSegmentedControl.titleForSegment(at: itemSegmentedControl.selectedSegmentIndex)!
segmentArray.append(Int(segChange)!)
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
stackView.addSubview(textView)
stackView.addSubview(itemSegmentedControl)
addSubview(stackView)
stackView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
stackView.widthAnchor.constraint(equalToConstant: 400).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 85).isActive = true
itemSegmentedControl.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
itemSegmentedControl.topAnchor.constraint(equalTo: textView.bottomAnchor, constant: 5).isActive = true
itemSegmentedControl.widthAnchor.constraint(equalTo: textView.widthAnchor).isActive = true
itemSegmentedControl.heightAnchor.constraint(equalToConstant: 40).isActive = true
textView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -10).isActive = true
textView.topAnchor.constraint(equalTo: stackView.topAnchor).isActive = true
textView.widthAnchor.constraint(equalTo: stackView.widthAnchor).isActive = true
textView.heightAnchor.constraint(equalToConstant: 40).isActive = true
}
}
Regards and thanks.
First: in the ViewController I added the empty array:
// I moved the segment array from the custom cell class to here
var segmentArray: [Int:Int] = [:]
override func viewDidLoad() {
super.viewDidLoad()
// I made this array to store default state to the items segments
segmentArray = [0:0,1:0,2:0]
}
// with this func I can update the segmentArray
func getSegmentNumber(r: Int, s: Int) {
segmentArray[r] = Int(s)
}
#IBAction func saveButtonAction(_ sender: UIButton) {
// Here I can save the new state for the segment to the firebase or anywhere.
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
cell.textView.text = arr[indexPath.row]
// here to store the index path row
cell.rowIndexPath = indexPath.row
// you need to add the link because it won't work
cell.link = self
return cell
}
Second: in the Custom Cell Class:
// I removed the store array
var segmentArray: [Int] = [] // removed
// I made link to the ViewController
var link: ViewController?
// variable to get the index path row
var rowIndexPath: Int?
// here I can update the state of the segment using the link
#objc func handlePointChange() {
let segChange = itemSegmentedControl.selectedSegmentIndex
link?.getSegmentNumber(r: rowIndexPath!, s: segChange)
}
That worked for me, and I hope you got it.
If you need anything you can ask.
Again thanks to Kudos.
Regards to all
Related
I am very very new to Swift programming and I am growing to dislike it very much. I am not grasping it aa easily as other languages.
I have a project I am working on and I cannot figure out what is wrong or why its isn't working.
In one view, I have a table view that has a cell. I am using an array to store all the values that I want to be stored in the corresponding elements in the table view.
When the user clicks on an individual cell in the table view, it will bring them to another view displaying other elements of the movie (runtime, image, director and year).
I have a template that I am using to code this and I think I have done everything correctly, but when I run the app, nothing shows.
I just want the table cells to show on startup, when I run the app. I can even troubleshoot myself if I can just have the table cells show.
Since I am so new to this language and XCode, I am having trouble navigating the IDE to find my issues. On top of much I am already struggling with Swift.
I could really use the help, if possible!
Here is all the code I have done:
import UIKit
class ViewController: UIViewController,
UITableViewDelegate,
UITableViewDataSource {
let movieList = ["Step Brothers", "Pulp Fiction", "Ali", "Harry Potter"]
let yearList = ["2008", "1994", "2001", "2001"]
let images = ["step_brothers", "pulp_fiction", "ali", "harry_potter3"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return movieList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let tempCell: TableViewCell = tableView.dequeueReusableCell(withIdentifier:
"cell") as! TableViewCell
tempCell.movieTitleLabel.text = movieList[indexPath.row]
tempCell.movieYearLabel.text = yearList[indexPath.row]
tempCell.movieImage.image = UIImage(named: images[indexPath.row] + ".jpeg")
return tempCell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let detailVC:MovieDetailViewController = self.storyboard?.instantiateViewController(withIdentifier: "MovieDetailViewController") as! MovieDetailViewController
// assign the values to the local variable declared in ProductDetailViewController Class
detailVC.movieImage = UIImage(named: images[indexPath.row] + ".jpeg")!
// make it navigate to ProductDetailViewController
self.navigationController?.pushViewController(detailVC, animated: true)
}
}
This is for the individual cell in the table view:
import UIKit
class TableViewCell: UITableViewCell {
#IBOutlet weak var movieTitleLabel: UILabel!
#IBOutlet weak var movieYearLabel: UILabel!
#IBOutlet weak var movieImage: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
This is the MovieDetailViewController:
class MovieDetailViewController: UIViewController {
#IBOutlet weak var movieDetailImage: UIImageView!
#IBOutlet weak var runtimeLabel: UILabel!
#IBOutlet weak var yearDetailLabel: UILabel!
#IBOutlet weak var directorDetailLabel: UILabel!
var runtime: String! // holds the product name
var year: String! // holds the price
var movieImage: UIImage! // holds the product image
var director: String!
override func viewDidLoad() {
super.viewDidLoad()
movieDetailImage.image = movieImage
runtimeLabel.text = runtime
yearDetailLabel.text = year
directorDetailLabel.text = director
// Do any additional setup after loading the view.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before 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.
}
*/
}
This is the error shown in the terminal, but there are no actual errors in the code:
2022-11-14 17:39:28.232645-0500 Exercise01[25678:1217794] [Storyboard] Unable to find method -[(null) TableViewCell] 2022-11-14 17:39:28.259975-0500 Exercise01[25678:1217794] [Assert] UINavigationBar decoded as unlocked for UINavigationController, or navigationBar delegate set up incorrectly. Inconsistent configuration may cause problems. navigationController=<UINavigationController: 0x141012400>, navigationBar=<UINavigationBar: 0x142106160; frame = (0 47; 0 50); opaque = NO; autoresize = W; layer = <CALayer: 0x600001d72280>> delegate=0x141012400
I can throw in the AppDelegate and SceneDelegate if you need it, just let me know.
Thank you everyone, again! I greatly appreciate the help!
I'd first recommend to get rid of Storyboards, they're error prone and debugging is a hell.
Your problem: I don't see you connecting the UITableView to the ViewController (with an IBOutlet if you use storyboards).
What you need to do is setting the UITableViewDelegate and the UITableViewDataSource for that UITableView
class ViewController: UIViewController,
UITableViewDelegate,
UITableViewDataSource {
let movieList = ["Step Brothers", "Pulp Fiction", "Ali", "Harry Potter"]
let yearList = ["2008", "1994", "2001", "2001"]
let images = ["step_brothers", "pulp_fiction", "ali", "harry_potter3"]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
I have tried your code, and I didn't get any errors, I hope you have set the data source and delegate of table view. Just add this to your viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
I think that the delegate and datasource aren't the only errors...
This is an example (complete code) for how you can do it programmatically:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let movieList = ["Step Brothers", "Pulp Fiction", "Ali", "Harry Potter"]
let yearList = ["2008", "1994", "2001", "2001"]
let images = ["step_brothers", "pulp_fiction", "ali", "harry_potter3"]
let tableView = UITableView() // declare tableView
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
tableView.backgroundColor = .white
tableView.translatesAutoresizingMaskIntoConstraints = false // disable defaults constrints
// set delegate and datasource
tableView.delegate = self
tableView.dataSource = self
tableView.register(TableViewCell.self, forCellReuseIdentifier: "cell") // register your cell
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return movieList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let tempCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell // get cellId and indexPath
tempCell.movieTitleLabel.text = movieList[indexPath.row]
tempCell.movieYearLabel.text = yearList[indexPath.row]
tempCell.movieImage.image = UIImage(named: images[indexPath.row])
return tempCell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80 // height of single row
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! TableViewCell // This is your cell in didSelectRow to obtain objects in there
let detailVC = MovieDetailViewController()
detailVC.movieImage = cell.movieImage.image ?? UIImage()
detailVC.runtimeLabel.text = cell.movieTitleLabel.text
detailVC.yearDetailLabel.text = cell.movieYearLabel.text
navigationController?.pushViewController(detailVC, animated: true)
}
}
this is your cell:
class TableViewCell: UITableViewCell {
let movieTitleLabel = UILabel()
let movieYearLabel = UILabel()
let movieImage = UIImageView()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.backgroundColor = .white
movieTitleLabel.textColor = .black
movieYearLabel.textColor = .black
movieTitleLabel.translatesAutoresizingMaskIntoConstraints = false
movieYearLabel.translatesAutoresizingMaskIntoConstraints = false
movieImage.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(movieImage)
movieImage.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
movieImage.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
movieImage.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
movieImage.widthAnchor.constraint(equalTo: contentView.heightAnchor).isActive = true
let stackView = UIStackView(arrangedSubviews: [movieTitleLabel, movieYearLabel])
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(stackView)
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10).isActive = true
stackView.leadingAnchor.constraint(equalTo: movieImage.trailingAnchor, constant: 10).isActive = true
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10).isActive = true
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
this is your movieDetailController:
class MovieDetailViewController: UIViewController {
let movieDetailImage = UIImageView()
let runtimeLabel = UILabel()
let yearDetailLabel = UILabel()
let runtime = String() // holds the product name
var year = String() // holds the price
var movieImage = UIImage() // holds the product image
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
movieDetailImage.translatesAutoresizingMaskIntoConstraints = false
runtimeLabel.translatesAutoresizingMaskIntoConstraints = false
yearDetailLabel.translatesAutoresizingMaskIntoConstraints = false
movieDetailImage.image = movieImage
movieDetailImage.contentMode = .scaleAspectFill
runtimeLabel.textColor = .black
runtimeLabel.textAlignment = .center
yearDetailLabel.textColor = .black
yearDetailLabel.textAlignment = .center
view.addSubview(movieDetailImage)
movieDetailImage.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
movieDetailImage.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
movieDetailImage.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
movieDetailImage.heightAnchor.constraint(equalTo: movieDetailImage.widthAnchor).isActive = true
let stackView = UIStackView(arrangedSubviews: [runtimeLabel, yearDetailLabel])
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
stackView.topAnchor.constraint(equalTo: movieDetailImage.bottomAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: movieDetailImage.leadingAnchor).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 60).isActive = true
stackView.trailingAnchor.constraint(equalTo: movieDetailImage.trailingAnchor).isActive = true
}
}
And This is the result (there isn't any array for directorDetailLabel)
I'm pretty new to iOS dev and I have an issue with UITableViewCell.
I guess it is related to dequeuing reusable cell.
I added an UIImageView to my custom table view cell and also added a tap gesture to make like/unlike function (image changes from an empty heart(unlike) to a filled heart(like) as tapped and reverse). The problem is when I scroll down, some of the cells are automatically tapped. I found out why this is happening, but still don't know how to fix it appropriately.
Below are my codes,
ViewController
import UIKit
struct CellData {
var title: String
var done: Bool
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var models = [CellData]()
private let tableView: UITableView = {
let table = UITableView()
table.register(TableViewCell.self, forCellReuseIdentifier: TableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.delegate = self
tableView.dataSource = self
configure()
}
private func configure() {
self.models = Array(0...50).compactMap({
CellData(title: "\($0)", done: false)
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = models[indexPath.row]
guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier, for: indexPath) as? TableViewCell else {
return UITableViewCell()
}
cell.textLabel?.text = model.title
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
tableView.reloadData()
}
}
TableViewCell
import UIKit
class TableViewCell: UITableViewCell {
let mainVC = ViewController()
static let identifier = "TableViewCell"
let likeImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "heart")
imageView.tintColor = .darkGray
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(likeImage)
layout()
//Tap Gesture Recognizer 실행하기
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))
likeImage.addGestureRecognizer(tapGestureRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
}
private func layout() {
likeImage.widthAnchor.constraint(equalToConstant: 30).isActive = true
likeImage.heightAnchor.constraint(equalToConstant: 30).isActive = true
likeImage.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
likeImage.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
}
#objc func didTapImageView(_ sender: UITapGestureRecognizer) {
if likeImage.image == UIImage(systemName: "heart.fill"){
likeImage.image = UIImage(systemName: "heart")
likeImage.tintColor = .darkGray
} else {
likeImage.image = UIImage(systemName: "heart.fill")
likeImage.tintColor = .systemRed
}
}
}
This gif shows how it works now.
enter image description here
I've tried to use "done" property in CellData structure to capture the status of the uiimageview but failed (didn't know how to use that in the correct way).
I would be so happy if anyone can help this!
You've already figured out that the problem is cell reuse.
When you dequeue a cell to be shown, you are already setting the cell label's text based on your data:
cell.textLabel?.text = model.title
you also need to tell the cell whether to show the empty or filled heart image.
And, when the user taps that image, your cell needs to tell the controller to update the .done property of your data model.
That can be done with either a protocol/delegate pattern or, more commonly (particularly with Swift), using a closure.
Here's a quick modification of the code you posted... the comments should give you a good idea of what's going on:
struct CellData {
var title: String
var done: Bool
}
class ShinViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var models = [CellData]()
private let tableView: UITableView = {
let table = UITableView()
table.register(ShinTableViewCell.self, forCellReuseIdentifier: ShinTableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.delegate = self
tableView.dataSource = self
configure()
}
private func configure() {
self.models = Array(0...50).compactMap({
CellData(title: "\($0)", done: false)
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: ShinTableViewCell.identifier, for: indexPath) as! ShinTableViewCell
let model = models[indexPath.row]
cell.myLabel.text = model.title
// set the "heart" to true/false
cell.isLiked = model.done
// closure
cell.callback = { [weak self] theCell, isLiked in
guard let self = self,
let pth = self.tableView.indexPath(for: theCell)
else { return }
// update our data
self.models[pth.row].done = isLiked
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
class ShinTableViewCell: UITableViewCell {
// we'll use this closure to communicate with the controller
var callback: ((UITableViewCell, Bool) -> ())?
static let identifier = "TableViewCell"
let likeImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "heart")
imageView.tintColor = .darkGray
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
let myLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// we'll load the heart images once in init
// instead of loading them every time they change
var likeIMG: UIImage!
var unlikeIMG: UIImage!
var isLiked: Bool = false {
didSet {
// update the image in the image view
likeImageView.image = isLiked ? likeIMG : unlikeIMG
// update the tint
likeImageView.tintColor = isLiked ? .systemRed : .darkGray
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// make sure we load the heart images
guard let img1 = UIImage(systemName: "heart"),
let img2 = UIImage(systemName: "heart.fill")
else {
fatalError("Could not load the heart images!!!")
}
unlikeIMG = img1
likeIMG = img2
// add label and image view
contentView.addSubview(myLabel)
contentView.addSubview(likeImageView)
layout()
//Tap Gesture Recognizer 실행하기
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))
likeImageView.addGestureRecognizer(tapGestureRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
}
private func layout() {
// let's use the "built-in" margins guide
let g = contentView.layoutMarginsGuide
// image view bottom constraint
let bottomConstraint = likeImageView.bottomAnchor.constraint(equalTo: g.bottomAnchor)
// this will avoid auto-layout complaints
bottomConstraint.priority = .required - 1
NSLayoutConstraint.activate([
// constrain label leading
myLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
// center the label vertically
myLabel.centerYAnchor.constraint(equalTo: g.centerYAnchor),
// constrain image view trailing
likeImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// constrain image view to 30 x 30
likeImageView.widthAnchor.constraint(equalToConstant: 30),
likeImageView.heightAnchor.constraint(equalTo: likeImageView.widthAnchor),
// constrain image view top
likeImageView.topAnchor.constraint(equalTo: g.topAnchor),
// activate image view bottom constraint
bottomConstraint,
])
}
#objc func didTapImageView(_ sender: UITapGestureRecognizer) {
// toggle isLiked (true/false)
isLiked.toggle()
// inform the controller, so it can update the data
callback?(self, isLiked)
}
}
I am receiving data from API . The data field is displayed with two Label control, Id and status . Here is the screenshot .
I have another view where I created switch button and label programatically. I want to change the status to false when user turn on to switch button but it now updating the value . Here is the code and function i defined .
class DetailsViewController : UIViewController{
#IBOutlet private weak var tableView: UITableView!
var changeStatus: ((Bool, String) -> Void)?
var identifier = ""
private let switchControl: UISwitch = {
let switchControl = UISwitch()
switchControl.translatesAutoresizingMaskIntoConstraints = false
switchControl.isOn = false
return switchControl
}()
#IBOutlet weak var customSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.addSubview(switchControl)
switchControl.addTarget(self, action: #selector(changeSwitchControl), for: .valueChanged)
let safeArea = view.safeAreaLayoutGuide
switchControl.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
switchControl.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
setUpUI()
}
#objc
private func changeSwitchControl() {
changeStatus?(switchControl.isOn, identifier)
}
}
Here is code in View Controller .
class ViewController: UIViewController {
// ! Mark is means it not null the value will in story board
// use lazy property to tell compiler instance value of the datasource self class and execuate the controller code to set the value of tableview datasource
private let viewModel = ViewModel()
private var subcriber = Set<AnyCancellable>()
private var storiesTrue = [String]()
#Published private(set) var stories = [Rover]()
private var datasourceStories = [Rover]()
private lazy var tableView: UITableView = {
let tableview = UITableView()
tableview.translatesAutoresizingMaskIntoConstraints = false// adding constrains
tableview.dataSource = self
tableview.showsVerticalScrollIndicator = false
tableview.register(StoryCell.self, forCellReuseIdentifier: StoryCell.identifier)
return tableview
}()
override func viewDidLoad() {
super.viewDidLoad()
setUpUI()
setUpBinding()
tableView.dataSource = self
tableView.delegate = self
self.tableView.rowHeight = 44;
// display the second view controller using push methods
/*let detail = DetailsViewController()
detail.name "Mohammad"
navigationController?.pushViewController(detail, animated: true)*/
}
private func setUpUI() {
view.backgroundColor = .white
view.addSubview(tableView)// adding hierracy key like adding into story board
// creating constrains with safe area
let safeArea = view.safeAreaLayoutGuide
tableView.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
}
private func setUpBinding(){
viewModel
.$rovers
.receive(on : RunLoop.main)
.sink {[weak self]_ in
self?.tableView.reloadData()
}
.store(in: &subcriber)
viewModel.getStories()
}
private func getStatus(by identifier: String) -> Bool {
return storiesTrue.contains(identifier)
}
}
extension ViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.rovers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: StoryCell.identifier , for: indexPath) as? StoryCell
else{ return UITableViewCell()}
let row = indexPath.row
let Id = viewModel.getId(by: row)
let title = viewModel.getTitle(by: row)
let identifier = viewModel.getIdentifier(by: indexPath.row)
let status = getStatus(by: identifier) ? "active" : "false"
cell.configureCell(Id: Id,title: title,statusString: status)
return cell
}
}
extension ViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
viewModel.getStories()
}
}
extension ViewController : UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let second = DetailsViewController()
navigationController?.pushViewController(second, animated: true)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 44;
}
}
Here is the view controller to defined the UI properties .
class StoryCell: UITableViewCell {
static let identifier = "StoryCell"
private lazy var storyIdLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .left
return label
}()
public lazy var storyTitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .left
return label
}()
private lazy var statusStory: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .left
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(Id: Int,title: String,statusString: String) {
storyIdLabel.text = "Id: \(String(Id))"
storyTitleLabel.text = "Status :\(title)"
statusStory.text = "Status:\(statusString)"
}
/* func configureCell(Id: Int) {
storyIdLabel.text = "Id: \(String(Id))"
}*/
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
private func setUpUI() {
contentView.addSubview(storyTitleLabel)
// constraints
let safeArea = contentView.safeAreaLayoutGuide
storyTitleLabel.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
storyTitleLabel.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
storyTitleLabel.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
contentView.addSubview(storyIdLabel)
let safeArea1 = contentView.safeAreaLayoutGuide
storyIdLabel.topAnchor.constraint(equalTo: storyTitleLabel.topAnchor).constant = 5
storyIdLabel.bottomAnchor.constraint(equalTo: safeArea1.bottomAnchor).isActive = true
storyIdLabel.leadingAnchor.constraint(equalTo: safeArea1.leadingAnchor).isActive = true
storyIdLabel.trailingAnchor.constraint(equalTo: safeArea1.trailingAnchor).isActive = true
/* contentView.addSubview(statusStory)
let safeArea2 = contentView.safeAreaLayoutGuide
statusStory.topAnchor.constraint(equalTo: statusStory.topAnchor).isActive = true
statusStory.bottomAnchor.constraint(equalTo: safeArea2.bottomAnchor).isActive = true
statusStory.leadingAnchor.constraint(equalTo: safeArea2.leadingAnchor).isActive = true
statusStory.trailingAnchor.constraint(equalTo: safeArea2.trailingAnchor).isActive = true*/
}
}
You have to use tableView.reloadData() after updating info that you want to show. Without running this function, data in table view won't be updated.
I have a view controller with a tableview. I want to register my tableview with a custom uitableviewcell that i created programmatically with a xib file. inside the custom cell i have a function that would change the outlets labels values and this is where i am getting a fatal found nil error. here is my code for the view controller.
class AllTasksViewController: UIViewController{
var tableView = UITableView()
func configureTableView(){
view.addSubview(tableView)
setTableViewDelegates()
tableView.rowHeight = 50
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
tableView.register(CustomCellNSB2.self, forCellReuseIdentifier: "CustomCellNSB2")
}
func setTableViewDelegates(){
tableView.delegate = self
tableView.dataSource = self
}
override func viewDidLoad(){
super.viewDidLoad()
configureTableView()
print("allTasksView loaded")
//add some positioning and size constraints here for allTasksView
}
}
extension AllTasksViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allTasks.count
}
// Cell Data and Configuration
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let task = allTasks[indexPath.row] // creating a new task from the already stored task depending on the indexpath.row if indexPath.row is 3 then the task is tasks[3]
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCellNSB2", for: indexPath) as! CustomCellNSB2 // setting the identifier ( we have already set in the storyboard, the class of our cells to be our custom cell)
cell.setTask(task: task) `// this is where my app is crashing`
if (task.value(forKey: "isComplete") as! Bool == true){
cell.labelsToWhite()
cell.backgroundColor = Colors.greencomplete
cell.selectionStyle = .none
} else {
cell.backgroundColor = .white //adjust for nightmode later
cell.labelsToBlack()
}
print("CellData Task :", task.value(forKey: "isComplete") as! Bool, task.value(forKey: "name") as! String)
// if !(task.value(forKey: "isComplete") as! Bool){
// cell.backgroundColor = task.isImportant ? .purple : .white //according to the task isImportant attribute we set the background color
// }
return cell
}
and here is my custom uitableview cell
import UIKit
import CoreData
class CustomCellNSB2: UITableViewCell {
#IBOutlet weak var taskLabel: UILabel!
#IBOutlet weak var dateLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func setTask(task: NSManagedObject ){ debugger leads me here. this is where it's getting nil
taskLabel.text = task.value(forKey: "name") as? String
dateLabel.text = task.value(forKey: "date") as?
String
}
func labelsToWhite() {
taskLabel.textColor = .white
dateLabel.textColor = .white
}
func labelsToBlack() {
taskLabel.textColor = .black
dateLabel.textColor = .red
}
}
You have to register your NIB ... all you are doing is registering the class.
Change this:
tableView.register(CustomCellNSB2.self, forCellReuseIdentifier: "CustomCellNSB2")
to this:
tableView.register(UINib(nibName: "CustomCellNSB2", bundle: nil), forCellReuseIdentifier: "CustomCellNSB2")
If you have the xib/nib configured correctly, that may be all you need to do.
I'm trying to build a tableView with a custom cell using UITableView programmatically without using a Storyboard but after I finish the tutorial and try to run the app the CustomCell I build,
The custom cell shows but once I click on tableView the custom cell hiding
Can someone check if I miss something?
Or maybe it's wrong to use xib with programmatically made tableView?
HomeViewController.swift
class HomeViewController: UIViewController {
fileprivate let homeTableView: UITableView = {
let htm = UITableView()
htm.translatesAutoresizingMaskIntoConstraints = false
return htm
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
homeTableView.register(CusTableViewCell.nib, forCellReuseIdentifier: CusTableViewCell.identifier)
setupTableView()
}
func setupTableView() {
let viewModel = HomeViewModel()
homeTableView.delegate = viewModel
homeTableView.dataSource = viewModel
homeTableView.rowHeight = 100
view.addSubview(homeTableView)
// homeTableView.separatorStyle = .none
homeTableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
homeTableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
homeTableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
homeTableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
}
}
CusTableViewCell.swift
class CusTableViewCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
static var nib:UINib {
return UINib(nibName: identifier, bundle: nil)
}
static var identifier: String {
return String(describing: self)
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
nameLabel.text = "Something"
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
HomeViewModel.swift
extension HomeViewModel: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: CusTableViewCell.identifier, for: indexPath) as? CusTableViewCell {
cell.backgroundColor = UIColor(red:0.17, green:0.73, blue:0.83, alpha:1.0)
return cell
}
return UITableViewCell()
}
}
The problem is related to weak reference to viewModel.
Put viewModel variable to the controller class as follows:
class HomeViewController {
var viewModel: HomeViewModel!
...
func setupTableView() {
self.viewModel = HomeViewModel()
...
}
}