Choppy animation while pushing UITableViewController with static cells - ios

I am having a UITableViewController with static cells. When I am performing a push segue, the animation is somewhat choppy. I have figured out which line of code is giving the problem. In viewWillAppear(_:) method of the UITableViewController, I am setting self.tableview.isHidden = true. If I remove this line of code then it works fine. However, I need this line as I am making a network call and I want to show the tableview only after the response is received. Any solutions to this issue would be appreciated.

You should set the TableView's Hidden property from the Storyboard. You can find the checkbox for this under "View > Drawing" in Attributes inspector. You can find the screenshot for this here.
That being said, you should find a better approach to indicate that API calls are being made. I would use a protocol that your viewcontrollers could conform to.
protocol ActivityIndicating {
func showLoading()
func hideLoading()
}
And in your ViewController class, you would have something like this
class ViewController: UIViewController, ActivityIndicating {
//protocol methods
func showLoading() {
//implement logic to hide tableview, show indicator, etc.
}
func hideLoading() {
//implement logic to show tableview, hide indicator, etc.
}
func someFunctionThatMakesAPIcalls() {
showLoading()
//makeAPICall and call hideLoading() once the api succeeds or fails
}
}

Related

How to save data from another view controller and reload tableview?

I'm making an expense tracking app. I have added a table view in the main view controller, and added an "add" button in the navigation controller. On clicking this, it shows a view controller in which you type in the data.
On clicking add at the end, it should save the entered data through coreData, and then be presented in the tableView, but my app crashes saying that a nil value was found, even though I have integrated the "??" safe guard."
You tableview in startingViewController is nil this is the problem. When you call the MainVC.getAllItems() your tabview is not initialized. Probably you are re creating startingViewController on your second controller to reach it getAllItems function but it is a wrong approach. You need to update previous viewController datas with protocols or notifications.
First you need to create a protocol like below
protocol AddViewControllerDelegate {
func updateTableView()
}
After that you need to define a variable in your addViewcontroller with this protocol type and call protocol's function when user adds new expense.
class AddViewController: UIViewController {
var delegate: AddViewControllerDelegate?
func callUpdateTableView() {
delegate?.updateTableView()
}
}
In your StartingViewController must conform this protocol. So you need to add updateTableView function. Also you need to say the delegate of your second class is your first class in where you show your addViewController.
class StartingViewController: UIViewController, AddViewControllerDelegate {
func goToAddViewController() {
let vc = AddViewController()
vc.delegate = self
show(vc, sender: nil)
}
func updateTableView() {
// Reload Tableview
}
}
So basically, when you call the protocol function from your secondViewController, your firstViewController's updateTableView function called and you can reload your tableview in this function.

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.

How to avoid a trail of delegate calls

I have a UIViewController which has a UICollectionView, inside that I have a ListSectionController class that controls a UICollectionViewCell, and inside that cell I have a UIView subclass.
When a button is pressed I need to call a method from the UIViewController. Currently I have a trail of delegate methods as it works it's way back to the view controller like this:
class MyView {
// delegate is the cell that the view is contained in
#IBAction func buttonPress() {
delegate?.myDelegateMethod()
}
}
extension MyCell : MyViewDelegate {
// The delegate is the section controller
func myDelegateMethod() {
delegate?.myDelegateMethod()
}
}
... etc
This seem like a lot of code duplication and a bit of a waste of space. How can I improve that?
When a button is pressed I need to call a method from the UIViewController
One way: give the button a nil-targeted action and implement the action method in the UIViewController. The message will arrive automatically.
For example, we give the button a nil-targeted action:
class Dummy {
#objc func buttonPressed(_:Any) {}
}
button.addTarget(nil,
action: #selector(Dummy.buttonPressed),
for: .touchUpInside)
And in the view controller we have:
#objc func buttonPressed(_ sender: Any) {
This will work, because the view controller is located up the responder chain from the button. This is exactly what nil-targeted actions are for.
Another way is to use NotificationCenter and Notification. I think that’s perfectly appropriate in this situation as well.
You can reach straight up the responder chain to any parent view or view controller in a generic, type safe way:
extension UIResponder {
func firstParent<T: UIResponder>(ofType type: T.Type ) -> T? {
return next as? T ?? next.flatMap { $0.firstParent(ofType: type) }
}
}
guard let ListSectionController = firstParent(ofType: ListSectionController) else {
return // we aren't in a ListSectionController
}
//Call ListSectionController methods here
I have encountered this condition so many time hence I would like to state obvious first:
"If your cell (and views inside it) are responsible of performing one and only one action (may be that is form button inside it)", you can use didSelectItemAtIndexPath". By designing your view with UIImageView. This approach have some UX issue such as, highlight and all but those can be handled with delegate as well.
If that is not the case and you cell is performing more than one action Mat's answer give best approaches.

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.

Sending information to a view I'm returning to in Navigation Controller

I have an app that includes a UIView in two different views. I want these two UIViews to show the same custom drawings, and have one be editable and one not be editable. When I segue from one view controller to the next (from the vc with the non-editable view to the editable one), I see the back button in UINavigation controller. Now, after I edit the view, I want the changes to be reflected in the non-editable view. I want to do this by being notified when the back button is pressed. So far, what I've tried hasn't worked:
public class BlockingViewController: UIViewController, UINavigationControllerDelegate {
func navigationController(navigationController: UINavigationController, viewController: UIViewController, animated: Bool) {
print("In function")
}
}
I took the navigationController method signature from https://developer.apple.com/library/ios/documentation/UIKit/reference/UINavigationControllerDelegate_Protocol/index.html but I'm not sure what changes I made that made it not work. I took out the optional declaration because including it gave me an error message stating "optional can only be applied to protocol members." I thought this was a protocol member, but I guess not.
That function declaration is incorrect. It should look like this,
func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
println("In function")
}
(or willShowViewController, if that's the one you want)

Resources