Swift: Update UITableView from UIButton inside custom UITableViewCell - swift

I have a UITableViewController called TableVC and a custom UITableViewCell called CustomCell. Inside the CustomCell class, I have a UIButton:
#IBAction func reloadTableView(sender: AnyObject) {
TableVC().tableView.reloadData()
}
When the corresponding button is tapped, it does not seem to update/reload my table view. Why not, and what can I do to resolve my issue?
Thanks!

You can get the tableView of a cell in several ways. The superview of your cell should be of type UITableView so in your cell you can (superview as? UITableView)?.reloadData().
However a more stable method is using the hierarchy of responders. I have created a really useful extension that allows you to find the next of responder of a particular type such a UITableView.
extension UIResponder {
func nextResponder<T: UIResponder>(ofType type: T.Type) -> T? {
switch nextResponder() {
case let responder as T:
return responder
case let .Some(responder):
return responder.nextResponder(ofType: type)
default:
return nil
}
}
}
This is a recursive function, so it climbs the responder hierarchy until it successfully casts a responder to the provided type or runs out of responders to try and cast.
#IBAction func reloadTableView(sender: AnyObject) {
nextResponder(ofType: UITableView.self)?.reloadData()
}

Whenever you are clicking a button you are creating a new instance of TableVC and you are reloading the that tableview data. instead of this store a TableVC object somewhere and reload data on that. either create a IBOutlet of TableVC or create a property in a class and assign TableVC ref to that.

use only
self.tableView.reloadData()

Related

how to properly pass data from one viewcontroller's selected cell to previous viewcontroller?

suppose I have two viewcontroller called A and B. In my viewcontroller B I have a tableview in it. When I selected a cell in my tableview, I want to pass that information back to A.
I have a dictionary that is of the following:
myData = [String: DataModel]
where DataModel takes the form of
struct DataModel{
var address = ""
var name = ""
}
I want to send the selected cell's key in B back to A. How should I go about doing that?
thanks for your help
Add this before class BViewController:
protocol ClassBViewControllerDelegate: class {
func didSelectTableViewCell(onRow row: Int)
}
Create a delegate property in BViewController:
weak var delegate: ClassBViewControllerDelegate?
Implement tableView delegate method tableView(_:didSelectRowAt:)
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath:NSIndexPath) {
let row = indexPath.row
delegate?.didSelectTableViewCell(onRow: row)
}
Tell ClassAViewController that its conforms to ClassBViewControllerDelegate as such:
class ClassAViewController: UIViewController, ClassBViewControllerDelegate {
Bind ClassAViewController and ClassBViewController at an appropriate place in ClassAViewController such a, for instance, prepareForSegue:sender:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SegueIdentifierXYZ" {
if let vc = segue.destination as? ClassBViewController {
vc.delegate = self
}
}
}
Use delegate method didSelectTableViewCell(onRow row: Int) of delegate contract ClassBViewControllerDelegate in ClassAViewController:
func didSelectTableViewCell(onRow row: Int) {
print("Selected table view row is:", row)
}
Using completion block or by using delegates you can achieve this.
Using blocks you can code like this:
In your B VC create one property for completion block.
var cellSelectionCallBackHandler: ((DataModel, Int) -> Void)?
Set the cellSelectionCallBackHandler property from VC A.
VCObjectB.cellSelectionCallBackHandler = { (data, index) in
// Use your data here
}
From VC B call the completion handler on selecting cell like this
cellSelectionCallBackHandler?(yourData, index)
Any doubt plz comment.
A delegate method can be used to achieve it. The place/method where you call to dismiss B call the delegate. You can implement this delegate in A.
You can also have a singleton dataHandler class, where you can set and get required properties and can access it from anywhere within your project.

(re)-Pass data after click on backbutton

I'm trying to pass data from a SecondViewController to my FirstViewController when I click on my back button (UINaviagtionController).
For pass my data from FirstViewController to the SecondViewController I do this:
if segue.identifier == "showSecondVC" {
let vc = segue.destination as! SecondViewController
vc.rows = rows[pathForAVC]
vc.lap = lapTime[pathForAVC]
vc.indexPath = pathForAVC
}
But I have no idea how to pass data from SecondViewController to the FirstViewController and I really don't understand topics about it on Stack Overflow.
I want to transfer it when I click here:
Thanks.
You can use delegate pattern for that. You can grab the back button press event like this and update the data
override func viewWillDisappear(_ animated: Bool) {
if self.isMovingFromParentViewController {
self.delegate.updateData( data)
}
}
For more information on delegates you can go through this.
Actually things depend on your requirement, if you want data to be updated in first view controller as soon as it is updated in second view controller, you would need to call delegate as soon as the data is updated. But as in the question you have mentioned that you want it to be updated on back button only, above is the place to do it.
Another way would be to have Datasource as singleton so that it is available to all the view controllers and the changes are reflected in all view controllers. But create singleton if absolutely necessary, because these nasty guys hang around for entire time your application is running.
You should have a custom protocol such as:
public protocol SendDataDelegate: class {
func sendData(_ dataArray:[String])
}
Here I suppose you want to send a single array back to FirstViewController
Then make your first view controller to conform to the custom protocol, such as:
class FirstViewController: UIViewController, SendDataDelegate
In the second view controller, create a delegate a variable for that protocol, such as:
weak var delegate: SendDataDelegate?
and then you catch the back action and inside it you call your custom protocol function, such as:
self.delegate?.sendData(arrayToSend)
In the first viewController, in the prepare for segue function just set the delegate like
vc.delegate = self

Set object properties using a UISwitch in a custom cell

I'm trying to do something that should be very simple, but I'm having issues due to my inexperience with Swift.
I have a ViewController that has a TableView inside of it with custom cells that are populated from an array of objects (called allListItems). These objects were created using Realm Model Object, which I'm using instead of Core Data, which I think might be pertinent. Each custom cell has a UISwitch in it, and ideally I'd like to set it up so that when the user toggles the UISwitch, it modifies the boolean isSelected property for that indexPath.row, and then appends that object to a separate array, called selectedListItems.
All of my searching through SO, Tuts+, and AppCoda has revealed that I should be using a protocol - delegate pattern here, with my protocol in my custom cell class and my delegate in my ViewController class. After flailing away at it for most of the day I haven't had any luck, however, which I think might be due to the arrays being Realm Model Objects.
As I mentioned, I'm very new to Swift and programming in general, so ELI5 responses are much appreciated! Thanks in advance!
For reference, here is my custom cell:
import UIKit
class AllListItemsTableViewCell: UITableViewCell {
#IBOutlet var toggleIsSelected: UISwitch!
#IBOutlet var listItemLabel: 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
}
}
Instead of the suggested protocol / delegate pattern use a callback.
This is very easy in Swift.
In the table view cell declare a optional variable with a closure
var callback : ((UITableViewCell, Bool) -> Void)?
and call it in the IBAction for the switch
#IBAction func switchChanged(sender : UISwitch) {
callback?(self, sender.on)
}
In cellForRowAtIndexPath set the callback
cell.callback = { (tableViewCell, switchState) in
if let indexPath = self.tableView.indexPathForCell(tableViewCell) {
// do something with index path and switch state
}
}
To pass the cell back can be useful if the cell was moved meanwhile to get the most recent index path.

Swift – Using popViewController and passing data to the ViewController you're returning to

I have an optional bool variable called showSettings on my first view controller which is called ViewController, and I'm popping from SecondViewController back to ViewController.
Before I pop, I want to set the bool to true. Seems wrong to instantiate another view controller since ViewController is in memory.
What's the best way to do this? I'm not using storyboards, if that's important for your answer.
Thanks for your help
So I figured it out, based mostly from this post – http://makeapppie.com/2014/09/15/swift-swift-programmatic-navigation-view-controllers-in-swift/
In SecondViewController, above the class declaration, add this code:
protocol SecondVCDelegate {
func didFinishSecondVC(controller: SecondViewController)
}
Then inside of SecondViewContoller add a class variable:
var delegate: MeditationVCDelegate! = nil
Then inside of your function that your button targets, add this:
self.navigationController?.popViewControllerAnimated(true)
delegate.didFinishSecondVC(self)
What we're doing here is doing the pop in SecondViewController, and not passing any data, but since we've defined a protocol, we're going to use that in ViewController to handle the data.
So next, in ViewController, add the protocol you defined in SecondViewController to the list of classes ViewController inherits from:
class ViewController: UIViewController, SecondVCDelegate { ... your code... }
You'll need to add the function we defined in the new protocol in order to make the compiler happy. Inside of ViewController's class, add this:
func didFinishSecondVC(controller: SecondViewController) {
self.myBoolVar = true
controller.navigationController?.popViewControllerAnimated(true)
}
In SecondViewController where we're calling didFinishSecondVC, we're calling this method inside of the ViewController class, the controller we're popping to. It's similar to if we wrote this code inside of SecondViewController but we've written it inside of ViewController and we're using a delegate to manage the messaging between the two.
Finally, in ViewController, in the function we're targeting to push to SecondViewController, add this code:
let secondVC = secondViewController()
secondVC.delegate = self
self.navigationController?.pushViewController(secondVC, animated: true)
That's it! You should be all set to pass code between two view controllers without using storyboards!
_ = self.navigationController?.popViewController(animated: true)
let previousViewController = self.navigationController?.viewControllers.last as! PreviousViewController
previousViewController.PropertyOrMethod
I came across this while looking for a way to do it. Since I use Storyboards more often, I found that I can get the array of controllers in the navigation stack, get the one just before the current one that's on top, check to see if it's my delegate, and if so, cast it as the delegate, set my methods, then pop myself from the stack. Although the code is in ObjC, it should be easily translatable to swift:
// we need to get the previous view controller
NSArray *array = self.navigationController.viewControllers;
if ( array.count > 1) {
UIViewController *controller = [array objectAtIndex:(array.count - 2)];
if ( [controller conformsToProtocol:#protocol(GenreSelectionDelegate)]) {
id<GenreSelectionDelegate> genreDelegate = (id<GenreSelectionDelegate>)controller;
[genreDelegate setGenre:_selectedGenre];
}
[self.navigationController popViewControllerAnimated:YES];
}
Expanding upon the answer by Abdul Baseer Khan:
For cases where the current view controller may have been loaded by different types of previous view controller, we can use the safer as? call instead of as!, which will return nil if the controller is not what we were looking for:
let previousVC = self.navigationController?.viewControllers.last as? AnExampleController
previousVC?.doSomething()
Although, you would need to repeat that for each different view controller that could load the current view controller.
So, you may want to, instead, implement a protocol to be assigned to all the possible previous view controllers:
protocol PreviousController: UIViewController {
func doSomething()
}
class AnExampleController: UIViewController, PreviousController {
// ...
func doSomething() {}
}
class AnotherController: UIViewController, PreviousController {
// ...
func doSomething() {}
}
class CurrentController: UIViewController {
// ...
func goBack() {
let previousVC = self.navigationController?.viewControllers.last as? PreviousController
previousVC?.doSomething()
}
}

Cannot load image from PARSE into PFTableViewCell's imageView property

I'm working with the PFQueryTableViewController and setting it up to find only friendships for this user which has been approved or sent to him.
"fromUser" and "toUser" are pointers to the user class, where I need the username and profilePicture from for each of the users contained in the queries results.
Now I try to fetch those in my cellForRowAtIndexPath method and load the image:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
let cellIdentifier = "contactCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! PFTableViewCell
if let user = object?["fromUser"] as? PFUser {
user.fetchInBackgroundWithBlock({ (user, error) -> Void in
if error != nil {
println("Could not fetch user object")
}
let user = user as! PFUser
cell.textLabel?.text = user.username!
cell.imageView?.file = user["profilePicture"] as? PFFile
cell.imageView?.loadInBackground()
})
} }
Getting the username to display in the tableView works just fine, but the image is actually never loaded. I tried different approaches to get the image loaded, but my cell's imageView property is always nil.
The prototype's class is set to PFTableViewCell
The controller is linked to the view in storyboard
Please let me know, if you guys have any idea why this built in property is nil and how to fix that.
Thanks,
Well I found a workaround which actually works quite good:
Create a prototype cell in your TableView (Storyboard) and set it's class to the normal "UITableViewCell"
Set the reuseIdentifier property of this cell to a value of your liking
Let your custom cell's file owner (I created a nib file for this cell) to be a subclass of PFTableViewCell
Create custom outlets for the textLabel and imageView
Register that Nib for the reuseIdentifier set in Step 2 in your TableViewController
Finally, use that class in your cellForRowAtIndexPath method like this:
let cell: PeopleTableViewCell! = tableView.dequeueReusableCellWithIdentifier("peopleCell") as? PeopleTableViewCell
I hope this will fix your problems too.
Regards,
[Edit]: It seems like SO doesn't like my code snippet to be formatted. It's embedded in code tags..
The PFTableViewCell does not work well with standard styles. So you have to make your cell a custom cell.
These re the steps I took to make it work (in Storyboard):
subclass PFTableViewCell with you own class
in the storyboard, customize the prototype cell using a custom cell
drag a UIImageView from the palette but then set its class as PFImageView
connect it to an IBOutlet in your PFQueryTableViewController subclass
implement cellForRowAtIndexPath: in the PFQueryTableViewController the way you did (your code was OK).
That way worked for me.