RxSwift: button click action from awakeFromNib to UIViewController - ios

I am Rx-Swift beginner. I am trying to get UIButton action which is present inside UITableViewCell.
Attempt:
I have tried with UITableViewCell's instance that I can get from UIViewController. Thats working fine.
Need:
How can I transfer that clicked tag value from awakeFromNib() to UIViewController ?
Code:
// UITableViewCell
class TripInfoCell: UITableViewCell {
#IBOutlet weak var btnMore: UIButton!
var cellbag = DisposeBag()
override func prepareForReuse() {
super.prepareForReuse()
cellbag = DisposeBag()
}
override func awakeFromNib() {
super.awakeFromNib()
btnMore.rx.tap.asObservable()
.subscribe { _ in
print("Tapping_ ", self.tag)
}.disposed(by: cellbag)
}
}
// UIViewController
override func viewDidLoad() {
super.viewDidLoad()
Observable.of(visitsModel).bind(to: tblView.rx.items(cellIdentifier: "cell", cellType: TripInfoCell.self)) { (row, element, cell) in
cell.tag = row
}
.disposed(by: disposeBag)
}
Attempt:
Here, UIButton action I have used inside UIViewController. So I can transfer data.
Observable.of(visitsModel).bind(to: tblView.rx.items(cellIdentifier: "cell", cellType: TripInfoCell.self)) { (row, element, cell) in
cell.tag = row
cell.btnMore.rx.tap.asObservable()
.subscribe { _ in
print("Inside_Tapping_ ", cell.tag)
}.disposed(by: cell.cellbag)
}

Related

How to access the value of view controller into the tableview inside tableview in Swift

I am new in Swift and I am not able to access IBOutlet variable in tableview cell inside tableview cell.
My code is something like this.
In view controller:
#IBOutlet var lblPopupTitle: UILabel!
In Tableview Inside Cell:
cell.btnEdit.tag = indexPath.item
cell.btnEdit.addTarget(self, action: #selector(btnEdit), for: .touchUpInside)
In Tableview Cell:
class AttendanceInOutCell: UITableViewCell {
#IBOutlet var txtStartAt: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
}
#objc func btnEditClick(_ sender: UIButton)
{
let index = IndexPath(row: sender.tag, section: 0)
let cell: AttendanceInsideCell = tableAway.cellForRow(at: index) as! AttendanceInsideCell
lblPopupTitle.text = cell.txtAwayStart.text
}
}
lblPopupTitle show me "Use of unresolved identifier 'lblPopupTitle'". How can I solve this issue?
There are several ways to achieve this.
Protocol- Delegate approach:
protocol PassDataDelagate: class {
func passtextFieldText(_ startText: String)
}
Make a protocol in class:
AttendanceInOutCell: UITableViewCell {
#IBOutlet var txtStartAt: UITextField!
weak var textDelegate: PassDataDelagate?
override func awakeFromNib() {
super.awakeFromNib()
}
#objc func btnEditClick(_ sender: UIButton)
{
let index = IndexPath(row: sender.tag, section: 0)
let cell: AttendanceInsideCell = tableAway.cellForRow(at: index) as! AttendanceInsideCell
textDelegate?.passtextFieldText(cell.txtAwayStart.text)
}
}
Inside Tableview Cell, add this line:
cell.btnEdit.tag = indexPath.item
cell.textDelegate = self
cell.btnEdit.addTarget(self, action: #selector(btnEdit), for: .touchUpInside)
Add this in view controller:
func passtextFieldText(_ startText: String) {
lblPopupTitle.text = startText
}
P.S: You can also pass multiple information in the delegate function if you want.
protocol AttendanceDelegate: class {
func didTapOnBtn(_ popUpTitle: String)
}
class AttendanceInOutCell: UITableViewCell {
#IBOutlet var txtStartAt: UITextField!
weak var delegate: AttendanceDelegate?
override func awakeFromNib() {
super.awakeFromNib()
}
#objc func btnEditClick(_ sender: UIButton)
{
let index = IndexPath(row: sender.tag, section: 0)
let cell: AttendanceInsideCell = tableAway.cellForRow(at: index) as! AttendanceInsideCell //( I'm not getting this from where you get this tableAway variable) // So I m just telling you how you can set lblpopupTitle.text in viewController
lblPopupTitle.text = cell.txtAwayStart.text // remove this line
self.delegate.didTapOnBtn(cell.txtAwayStart.text) // please unwrap the textfield text before passing it as parameter
}
}
// for view controller
// there must be as tableview cellforRowAt function where you are creating your cell
// so after creating the cell instance
// add this line ( cell.delegate = self) and return cell
// now add extension to your controller
extension ViewController: AttendanceDelegate {
func didTapOnBtn(_ popUpTitle: String) {
lblPopupTitle.text = popUpTitle
}
}

How to Print data from textfield present inside Tableview cell to console on tapping button

I want to print data from textfield present inside the tableview cell to console on clicking a button present outside the tableview.
I am getting data of only single row when i use the IBAction method of button for printing the data.
here is my updated code for reference.
//Deliverview cell
import UIKit
//Protocol
protocol Delivery {
func OnClick(index : Int)
}
class DeliveryViewCell: UITableViewCell {
#IBOutlet weak var cellTextField: UITextField!
#IBOutlet weak var button: UIButton!
#IBOutlet weak var cellImageView: UIImageView!
var celldelegate : Delivery?
var index : IndexPath?
var data: DeliveryData? {
didSet {
updateCell()
}
}
override func awakeFromNib() {
super.awakeFromNib()
cellTextField.borderStyle = .none
cellTextField.delegate = self
// Initialization code
}
func updateCell() {
if let data = data {
cellTextField.text = data.value
cellTextField.placeholder = data.placeholder
}
}
#IBAction func pickLocation(_ sender: Any) {
celldelegate?.OnClick(index: index!.row)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
extension DeliveryViewCell: UITextFieldDelegate {
func textFieldDidEndEditing(_ cellTextField: UITextField) {
deliveryData1.value = cellTextField.text!
}
}
//View controller
class DeliveryData {
let placeholder: String
var value: String = ""
init(_ placeholder: String) {
self.placeholder = placeholder
}
}
var deliveryData1: [DeliveryData] = [
DeliveryData("Name"),
DeliveryData("Phone"),
DeliveryData("Email"),
DeliveryData("order"),
DeliveryData("Address"),
DeliveryData("Date"),
DeliveryData("Description"),
DeliveryData("Barcode"),
DeliveryData("Image")
]
UITableViewCells are not meant to store state. As you scroll, the cells will be re-used, and any values they might have (ie, text the user has input) will be lost.
The solution to this is to save out any changes the user has made to a data model. This can be a class, an array, a dictionary, etc. In your case, DeliveryViewCell will need to conform to UITextFieldDelegate in order to be notified when the user has finished editing:
extension DeliveryViewCell: UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) {
//Do something here to save the text...
}
}
Then it's just a matter of storing the updated text somewhere. Here is one option, although by no means the only or even best way:
//In your cell
var data: DeliveryData? {
didSet {
updateCell()
}
}
override func awakeFromNib() {
super.awakeFromNib()
textField.delegate = self
}
func updateCell() {
if let data = data {
textField.text = data.value
textField.placeholder = data.placeholder
}
}
extension DeliveryViewCell: UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) {
deliveryData?.value = textField.text!
}
}
//In your view controller
class DeliveryData {
let placeholder: String
var value: String = ""
init(_ placeholder: String) {
self.placeholder = placeholder
}
}
var deliveryData: [DeliveryData] = [
DeliveryData("Name"),
DeliveryData("Phone"),
DeliveryData("Email"),
DeliveryData("order"),
DeliveryData("Address"),
DeliveryData("Date"),
DeliveryData("Description"),
DeliveryData("Barcode"),
DeliveryData("Image")
]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = deliveryTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! DeliveryViewCell
cell.data = deliveryData[indexPath.row]
return cell
}
#IBAction func tapSaveData(_ sender: Any) {
for data in deliveryData {
print(data.value)
}
}
This also has the added benefit of restoring any text the user types in when a cell gets scrolled back into view.
From the way you described the question I believe you are trying to print the value of a text field associated with a specific cell. You have several ways of achieving that.
Method 1 (easiest)
The easiest for beginners in this scenario is the following.
Change your cellForRowAt to do the following:
let cell = deliveryTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! DeliveryViewCell
cell.cellTextField.placeholder = deliveryData[indexPath.row]
//button refers to the button Outlet you have or should create.
cell.button.tag = indexPath.row
return cell
Then change your button's action to be:
#IBAction func tapSaveData(_ sender: UIButton) {
let row = sender.tag //The tag you saved when dequeuing the cell
let cell1 = deliveryTableView.cellForRow(at: IndexPath.init(row: row, section: 0)) as! DeliveryViewCell
print(cell1.cellTextField.text!)
}
Method 2
You can simply use a closure inside the cell.
So lets say this is part of your custom cell:
... your code
var onTextFieldEndEditing: ((String?) -> Void)?
func textFieldDidEndEditing(_ textField: UITextField) {
//when editing is done.
onTextFieldEndEditing?(textField.text)
}
Then, in your cellForRowAt:
let cell = deliveryTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! DeliveryViewCell
cell.cellTextField.placeholder = deliveryData[indexPath.row]
cell.onTextFieldEndEditing = { [weak self] text in
print(text)
}
return cell
In method 2 you don't need the #IBAction for the button anymore or the button itself.
If you still wish to invoke the print on button tap you can simply create an #IBAction inside the cell itself and then call the closure when the button is tapped instead of when the text field ends editing.
Let me know if you have any more questions.

RxSwift: Observing UITextFields from TableViewCell inside a child view controller

I have a tableView and a childController in a parent viewController, the tableView in the ParentViewController can have from 1 - 4 cells, each cell contains a UITextField.
The ChildController also have a TableView, that list results(autocomplete) based on what is inputted in any of the TextField in the ParentViewController tableView cell.
I want the childController to always listen to any of the UITextField and show the result on the tablView. This is what I have currently
private var query = Variable<String>("")
var queryDriver: Driver<String> {
return query.asDriver()
}
TableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView
.dequeueReusableCell(
withIdentifier: "StopCell", for: indexPath) as? StopCell else {
fatalError("Cannot dequeue StopCell")
}
cell.delegate = self
cell.locationTextField.rx.text.map {$0 ?? ""}
.bind(to: query)
.disposed(by: disposeBag)
cell.locationTextField.rx.controlEvent(.editingDidEnd)
.asDriver(onErrorJustReturn: ())
.drive(onNext: { [unowned self] in
cell.locationTextField.resignFirstResponder()
})
.disposed(by: disposeBag)
return cell
}
Add child controller
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let noteVC = NoteVc()
addChildController(viewController: noteVC)
}
NoteVC
class NoteVc: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(tableView)
viewModel = SearchLocationViewModel(query: <#T##SharedSequence<DriverSharingStrategy, String>#>)
}
ViewModel
class SearchLocationViewModel {
let disposeBag = DisposeBag()
// MARK: - Properties
var querying: Driver<Bool> { return _querying.asDriver() }
var locations: Driver<[Location]> { return _locations.asDriver() }
// MARK: -
var hasLocations: Bool { return numberOfLocations > 0 }
var numberOfLocations: Int { return _locations.value.count }
// MARK: -
private let _querying = BehaviorRelay<Bool>(value: false)
private let _locations = BehaviorRelay<[Location]>(value: [])
// MARK: -
private let disposeBag = DisposeBag()
// MARK: - Initializtion
init(query: Driver<String>) {
Behave.shared.queryDriver
.throttle(0.5)
.distinctUntilChanged()
.drive(onNext: { [weak self] (addressString) in
self?.geocode(addressString: addressString)
})
.disposed(by: disposeBag)
}
Like it is implemented in the Uber app, users can add up to three destinations, the yellow rectangle box in the image below is my ChildViewController
Here's the simplest I could think of. I made viewModel a global constant but you might want to get more elaborate with it.:
class ViewModel {
let inputs: [AnyObserver<String>]
let outputs: [Observable<String>]
init(count: Int) {
let subjects = (0..<count).map { _ in BehaviorSubject<String>(value: "") }
inputs = subjects.map { $0.asObserver() }
outputs = subjects.map { $0.asObservable() }
}
}
class ParentViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let bag = self.bag
Observable.just(viewModel.inputs)
.bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { _, element, cell in
let textField = cell.viewWithTag(99) as! UITextField
textField.rx.text.orEmpty
.bind(to: element)
.disposed(by: bag)
}
.disposed(by: bag)
}
let bag = DisposeBag()
}
class ChildViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let bag = self.bag
Observable.just(viewModel.outputs)
.bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { _, element, cell in
element
.bind(to: cell.textLabel!.rx.text)
.disposed(by: bag)
}
.disposed(by: bag)
}
let bag = DisposeBag()
}

Access to some values from another class in Swift

I have a TableViewCell class like this:
class CampaignsTableViewCell: UITableViewCell {
#IBOutlet weak var activateButton: UIButton!
#IBOutlet weak var titleCampaignPlaceholder: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
setUpButton()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
private func setUpButton(){
activateButton.backgroundColor = .clear
activateButton.layer.cornerRadius = 5
activateButton.layer.borderWidth = 1
activateButton.layer.borderColor = UIColor.blue.cgColor
}
}
And, in another class which is a ViewController I have my UITableView methods:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let rowNumber = indexPath.row
let cellIdentifier = "CampaignTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? CampaignsTableViewCell else {
fatalError("The dequeued cell is not an instance of TableViewCellController.")
}
cell.titleCampaignPlaceholder.text = campaignsArray[rowNumber].campaignName
return cell
}
I need to use my activateButton in my UITableView method in order to access to campaignsArray. I have another method which requieres values from that array, so I need that method is called every time activateButton is pressed from my UITableView.
Any idea ?
Thank you very much
What I like doing in those cases where you have a button inside your UITableViewCell is the following:
Give the cell a closure that is called when tapping on the button like so
class CampaignsTableViewCell: UITableViewCell {
... all your code....
// give your cell a closure that is called when the button is pressed
var onButtonPressed: ((_ sender: UIButton) -> ())?
#IBAction func buttonPressed(sender: UIButton) { // wire that one up in IB
onButtonPressed?(sender)
}
}
and then inside your TableViewController
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! CampaignsTableViewCell
cell.titleCampaignPlaceholder.text = campaignsArray[rowNumber].campaignName
cell.onButtonPressed = { [weak self] sender in
// Do your magic stuff here
}
return cell
Hope that helps
Your cell will get that event, not tableView. What you need to do is:
Create protocol inside your cell:
protocol CampaignsTableViewProtocol{
func actionButtonPressed(row: Int)
}
class CampaignsTableViewCell: UITableViewCell {
#IBOutlet weak var activateButton: UIButton!
#IBOutlet weak var titleCampaignPlaceholder: UILabel!
// keep info about row
var rowIndex: Int = -1
// create delegate that will let your tableView about action button in particular row
var delegate : CampaignsTableViewProtocol?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
setUpButton()
self. activateButton.addTarget(self, action: #selector(self.activatePressed), for: UIControlEvents.touchDown)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func activatePressed(){
self.delegate?.actionButtonPressed(row :rowIndex)
}
private func setUpButton(){
activateButton.backgroundColor = .clear
activateButton.layer.cornerRadius = 5
activateButton.layer.borderWidth = 1
activateButton.layer.borderColor = UIColor.blue.cgColor
}
}
Your tableViewController needs to adopt this protocol:
class MyTableViewController: UITableViewDelegate, UITableViewDataSource, CampaignsTableViewProtocol {
// rest of the code
}
Also, you will need to implement delegate function in your tableViewController:
func actionButtonPressed(row: Int) {
// get campaign you need
let campaign = campaignsArray[row]
// rest of the code
}

How to stop UITableView from reusing image cell data?

My Tableview is reusing previous cell image data and displaying images from previous cells. I did the following to prevent it from reusing. What am I missing?
#IBAction func cityBtn(_ sender: UIButton) {
for i in stride(from: 0, to: assignCities.count, by: 1)
{ cityName.append(bigCities[i])
cityImages.append(bigCityImages[i])
}}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let myCell= tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CityCell
myCell.myLabel.text = cityName[indexPath.row]
let cityImg = cityImages[indexPath.row]
myCell.myImage.image = nil
if (cityImg != "")
{
myCell.myImage.image = UIImage(named:cityImg)
}
else
{
myCell.myImage.image = nil
}
return myCell
}
import UIKit
class CityCell: UITableViewCell {
#IBOutlet weak var myImage: UIImageView!
#IBOutlet weak var myLabel: UILabel!
override func prepareForReuse() {
super.prepareForReuse()
myImage.image = nil
}
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
}
}
I did the above but still it is not getting the result
You can implement func prepareForReuse() method of UITableViewCell and reset your cell properties there.
This method is invoked just before the object is returned from the UITableView method dequeueReusableCell(withIdentifier:)
func prepareForReuse() {
super. prepareForReuse()
myImage.image = nil
}
This had to be implemented inside your custom table view cell class.
If there ara not so many cells in your tableView,maybe you can use unique CellIdentifier,such as: indexPath.section_indexPath.row

Resources