Using UITableViewController logics in UITableViewDataSource class - ios

I'm doing a refactor because my UITableViewController is bloating very badly. The first thing I'm trying to do is refactoring the data source out of the table view controller like this:
// In my UITableViewController
let ds = MyDataSource()
func viewDidLoad() {
tableView.dataSource = ds
}
In my data source class, I want to use some logic that I feel should belong in the table view controller.
class MyDataSource: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
// I want to use the variable showMore here
return showMore ? 2 : 1
}
// More data source doe
}
The variable showMore is a boolean variable defined in my table view controller and is updated by a button in the footer view of a section.
Question is then, if showMore belongs in the table view controller, how can I access it from my data source class? If it doesn't belong in the
table view controller, where does it belong and why?
Thanks!

First thought that comes to mind - and I'm just typing, not writing in Xcode...
// footerButtonTap
if let ds = tableView.dataSource as? MyDataSource {
ds.showMore = !ds.showMore
tableView.reloadData()
}
Or something along those lines might fit your needs.

UITableViewController is Apple's convenient implementation of a UIViewController that contains a tableView instead of a view, as well as conforming immediately to UITableViewDataSource and UITableViewDelegate. I'd recommend you just use a UIViewController and add a tableView, and then proceed as you have by setting the table's delegate and datasource to MyCustomTableController, which would conform to both UITableViewDataSource and UITableViewDelegate. All logic should be handled inside the controller.

Related

Setting up a ViewController for a .xib view

I am neither an iOS developer, nor a swift developer, but please bear with me:
I am currently trying to implement a simple iOS app but I have difficulties understanding how exactly I am supposed to set up custom UIViews and ViewControllers for those UIViews.
I am using a UIScrollView that is containing items a little bit more complex than just images, thats what I use custom views for.
What I did was:
I created a .xib file, the view itself. I added some elements (here it is only a textfield, for simplicity's sake).
I created a cocoa touch class "CustomView" that inherits from UIView and set my view up to be of that class (inside the class I just set up elements and such).
Now I want a ViewController that controls the class whenever it is rendered (for example reacting to the changing textField).
I cant manage everything from my main ViewController, because it would get too big (e.g. 3 scrollViews * 5 subviews that need to be managed).
I want a solution that uses ViewControllers for each subview (in case they themselves will have subviews, too).
How do I do that?
Do I need to add some sort of childViewController?
I really am at loss, most of the blog posts and SO examples simply do not work and/or are outdated and I am unsure about whether or not I got the whole View - ViewController pattern wrong.
Let's say you have two view controllers, MainViewController and TableViewController. TableVC's main view is to be a subview of MainVC's main view. In addition, you wish to pass back to MainVC which cell was selected in TableVC.
A solution is (a) make TableVC be a child to MainVC and (b) make MainVC be a delegate for TableVC.
TableViewController:
protocol TableVCDelegate {
func cellSelected(sender: TableViewController)
}
class TableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// please note that you can do delegation differently,
// this way results in crashes if delegate is nil!
var delegate:TableVCDelegate! = nil
var someValue = ""
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// set someValue to contents in the selected cell or it's data source
someValue = "Hello World!"
delegate.cellSelected(sender: self)
}
}
MainViewController:
class MainViewController: UIViewController, TableVCDelegate {
let tableVC = TableViewController()
override func viewDidLoad() {
// make tableVC be a child of this VC
addChild(tableVC)
tableVC.didMove(toParent: self)
tableVC.delegate = self
// position tableVC.view
tableVC.view.translatesAutoresizingMaskIntoConstraints = false
}
func cellSelected(sender: TableViewController) {
print(sender.someValue) // this should send "Hello World!" to the console
}
}
This is obviously untested code, but it is based on product code. This is meant to be a shell to help you get started.

Swift 3 UITableView Problems

My interface consists of two things inside my main ViewController: a label (the top half of the screen), and a table (the bottom half).
For the life of me, I cannot find a tutorial or example in Swift 3 + iOS 10 where a UITableView is successfully used, as opposed to a UITableViewController which takes up the entire screen.
What must be done to use a UITableView with dynamic cells in the bottom half of your interface?
I tried adding the UITableView, then creating a subclass of UITableViewController, but I was unable to select this in the UITableView's Custom Class -> Class dropdown. Apparently, I need to subclass UITableView, but I can find no examples of how to do this.
Use a standard UIViewController and drag a table view into the canvas.
In IB connect the table view to the IBOutlet and delegate and datasource to the controller.
class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var tableView : UITableView!
...
Unlike UITableViewController you have to implement all relevant datasource and delegate methods.
At first , the UITableView and UITableViewController are not the same thing, the TableView is a view and TableViewController is a Controller withe a tableview inside.
If you are using UITableViewController you should drag a UITableViewController, and then change its class in the third tab on the top!
If you are using a basic ViewController and drag a TableView inside, you could get The TableView in the ViewController using an IBOutlet.#John D.
You can use normal ViewController and init a UITableView in the viewDidLoad and add it to the ViewController.
var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView = UITableView.init(frame: CGRect(x:0, y: 300, height: 300, width:screenWidth))
self.view.addSubview(tableView)
self.tableView.register(UINib.init(nibName: "NameListTableViewCell", bundle: Bundle.main), forCellReuseIdentifier: nameListTableViewCellId)
// Do any additional setup after loading the view.
}
set the tableview.delegate = self and tableview.datasource = self,
using methods in the pic and lots other in the UITableViewDelegate and UITabelViewDataSource protocol to Custom your tableView!
hope this would help

Exchange Date between UITableViewController and ContentViewController with multiple pages

I am working on an application where I need to pass an array of values from a click of tableview cell in my tableviewcontroller which is the initial view controller to a contentview view controller with page curl transition.
Each of the page will have a textview which will be populated with value passed from the initial view controller and can be edited by the user.
My issue is, I am not able to update the array with the new value and pass it back to the initial view controller
I tried the following:
I implemented pageviewcontroller methods in my initialview controller to create instances of the ContentViewController with page curl transition and was able to pass values to each of the pages when the page curl was done. But I am trying to figure out a way to pass back the updated value to the tableviewcontroller from where I instantiated the object.
I tried Segue from tableview to the contentViewcontroller, but it does not work.
Appreciate if somebody can help me.
Use the delegate pattern.
Define a protocol within ContentViewController :
protocol UpdateModelDelegate {
func didDismissContentViewController(controller:ContentViewController)
}
Establish a delegate variable within ContentViewController :
var delegate: UpdateModelDelegate?
When you dismiss ContentViewController call delegate?.didDismissContentViewController(self) which will send the data back to your UITableViewController.
Have the UITableViewController conform to this protocol.
class MyTableViewControllerSubclass: UITableviewController, UpdateModelDelegate
When presenting the ContentViewController set your UITableViewSubclass as the delegate.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var contentViewController: ContentViewController()
//After you pass the necessary data to contentViewController...
contentViewController.delegate? = self
}
finally, implement the delegate function within your UITableviewController subclass.
func didDismissContentViewController(controller:ContentViewController) {
//You're passed in the contentViewController here, use relevant data to update your model.
}

Change value / image in view controller by selecting a cell within a collection view within an embedded container

In a detail view controller, I've a 'featureImage' in the top left, and a thin horizontal strip of images below this. The strip of images is an embedded container view managed by a custom CollectionViewController, which shows an array of images. The initial featureImage is the first image in an array of images[0], and the same array is passed to the collection view.
I'd like the featureImage to update to the same image if a cell in the container view is selected / tapped.
I guess I need to call the delegate method didSelectItemAtIndexPath, which will give me the indexPath. Right? But then how do I pass the indexPath, which is already from a delegate, back to the detail view controller.
EDITED - The code shows code overlap and differences between Responder Chain AND delegate approaches. Uncommented in the didSelectItemAtIndex path, the Responder Chain approach works, while the delegate approach does not.
Protocol defined and included at top of DetailViewController (I doesn't seem to matter which file the protocol is in, and is only typed to class to allow the delegate property to be 'weak'). Needed for both approaches.
protocol FeatureImageController: class {
func featureImageSelected(indexPath: NSIndexPath)
}
class DetailViewController: UIViewController, FeatureImageController {
Delegate property declared in the custom UICollectionViewController class. Only needed for delegate approach.
weak var delegate: FeatureImageController?
Delegate property initiated in the DetailViewController. Only needed for delegate approach.
override func viewDidLoad() {
super.viewDidLoad()
let photoCollectionVC = PhotoCollectionVC()
photoCollectionVC.delegate = self as FeatureImageController ... }
The Responder Chain (active) OR the delegate approach (commented out) within the collection view controllers didSelectItemAtIndexPath method.
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
{
if let imageSelector = targetForAction("featureImageSelected:", withSender: self) as? FeatureImageController {
imageSelector.featureImageSelected(indexPath)
}
// self.delegate?.featureImageSelected(indexPath)
}
Delegate method in DetailViewController. Needed for both.
func featureImageSelected(indexPath: NSIndexPath) {
record?.featureImage = record?.images[indexPath.row]
self.configureView()
}
The communication of data selection between View Controllers in my experience can best be achieved in two ways- the delegation or responder chain route. Either way the first step would be creating a protocol that your DetailViewController will adhere to. Something like:
protocol FeatureImageController: class {
func featureImageSelected(image: UIImage)
}
Your DetailViewController would then implement this function and use it to change the 'feature image'. How this is communicated then depends on whether you use delegation or the responder chain.
Delegation
If you prefer to use delegation then declare a delegate property on your CollectionViewController like so:
weak var delegate: FeatureImageController?
then in didSelectItemAtIndexPath you would determine the selected image using the provided indexPath and pass it to your delegate:
delegate?.featureImageSelected(selectedImage)
where selectedImage is the image selected from the collection view.
Responder Chain
If you decide to use the responder chain then you need not declare a delegate property. Instead you would ask for the first target that responds to your protocol method. So inside didSelectItemAtIndexPath you would say:
if let imageController = targetForAction("featureImageSelected:", withSender: self) as? FeatureImageController {
imageController.featureImageSelected(selectedImage)
}
Both methods (delegation or responder chain) allow the collection view controller to pass its selection to the detail controller. The delegation route is more common in the Framework but I find as we use containers within containers more often it becomes pretty nasty to properly manage the chain of delegates without an amount of 'coupling' I'm not comfortable with. The responder chain, on the other hand, is already provided by the framework to 'dig' into the hierarchy of controllers to find one willing to handle your action.

trying to understand protocols and delegates in swift

I'm trying to wrap my head around protocols and delegates, but seems to be having some issues. I have 2 ViewControllers that I'm trying to pass data from. ViewController A has a text field that I want to be optionally populated from ViewController B. So there is a button on ViewController A that segues you over to ViewController B This is how I have B set up.
protocol AddPlayersViewControllerDelegate{
var playersName:String? { set get }
}
class B-Controller: UIViewController, UITableViewDelegate, UITableViewDataSource{
var addPlayerDelegate:AddPlayersViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
..etc
}
I'm using this code in my viewControllers B class to dismiss the currentView when a cell is selected
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("did select")
let cell = playerTableView.cellForRowAtIndexPath(indexPath)
addPlayerDelegate?.playersName? = "New Name"
if let navController = self.navigationController {
navController.popViewControllerAnimated(true)
}
}
It's not allowing me to set the players Name property inside the protocol here. It keeps returning me Nil when I check it from ViewController A.
View Controller A looks like this:
class A-ViewController: UIViewController, UITextFieldDelegate, AddPlayersViewControllerDelegate{
var addPlayerDelegate:AddPlayersViewControllerDelegate?
}
//then I'm just trying to print out the new name the was set in ViewController B
override func viewWillAppear(animated: Bool) {
println("this is the selected players name \(addPlayerDelegate?.playersName)") - returns nil
}
I'm sure I'm not fully understanding something, but I feel that I just keep reading and trying out examples only to end back up here where I started from.
//************************* UPDATE *************************//
I'm going to try and simplify my set up. I have 2 View Controllers, VC-A, and VC-B.
VC-A has a text field and a button. VC-B has a tableview. I want the option to have the textField to be populated from the cell.text from CB-B, but only if the user taps the button to view VC-B. So the first time that VC-A loads, it should being back nil from my playersName string from the protocol, because VC-B has never been called as of yet. But once the user taps the button inside VC-A to view VB-B and then selected a cell, which would dismiss VC-B and populate the playersName string inside the protocol on the VC-B class, then I'm using the viewWillAppear method to check to see if playersName has been set and if so use it. Here is my updated code from the help you have given me.
VC-A
class FirstViewController: AddPlayersViewControllerDelegate{
var playersName:String?
let svc = LookUpViewController()
override func viewDidLoad() {
super.viewDidLoad()
svc.addPlayerDelegate = self
}
}
VC-B
protocol AddPlayersViewControllerDelegate{
var playersName:String? { set get }
}
class LookUpViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
var addPlayerDelegate: AddPlayersViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = playerTableView.cellForRowAtIndexPath(indexPath)
addPlayerDelegate?.playersName = "Ziggy"
println("the name to be pass is \(addPlayerDelegate?.playersName)")
if let navController = self.navigationController {
navController.popViewControllerAnimated(true)
}
}
It seems that I'm still getting nil even when I got back to VC-A from VC-B. All I want is to be able to get some data (string) from VC-B and use it in VC-A, but only after the user uses the VC-B class. Does that make sense?
You have shown that in the BController you have a property addPlayerDelegate:
var addPlayerDelegate:AddPlayersViewControllerDelegate?
And you have shown that in the BController you talk to that property:
addPlayerDelegate?.playersName? = "New Name"
But you have not shown that at any point in the lifetime of this controller, its addPlayerDelegate property is ever set to anything. For example I would want to see code like this:
someBController.addPlayerDelegate = someAController
If that doesn't happen, then that property remains at its initial value of nil.
Another problem with your code is that this line makes no sense:
class A-ViewController : // {
var addPlayerDelegate:AddPlayersViewControllerDelegate?
}
The AController doesn't need this property. They don't both need delegates! What the AController needs is a playersName property. Without it, it doesn't conform to the AddPlayersViewControllerDelegate protocol. In fact, I'm surprised that without that property your code even compiles. Are you sure you are reporting it correctly?

Resources