I am trying to show an activity indicator when a user taps a certain TabBar item. My problem, I think, lies in the fact that the UI Main thread is frozen.
When a user taps the TabBar I prepare a big data list that takes about six seconds. I get the activity to show everywhere but when they tap the TabBar.
It seems as though the indicator is "running" because when the segued uitableviewcontroller shows, it is showing the indicator. But this is too late and dispatch doesn't seem to do anything either.
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if let visibleViewCtrl = UIApplication.shared.keyWindow?.visibleViewController {
// do whatever you want with your `visibleViewCtrl`
print (String.init(describing: visibleViewCtrl.classForCoder))
DispatchQueue.main.async{
let aprogressView = ProgressView(Message: "Filtering...",
Theme:.Dark,
IsModal:true);
visibleViewCtrl.view.addSubview(aprogressView)
aprogressView.show()
}
}
return true
}
OK, So the problem is that it is going to a tableview controller, which tries to get rows in section almost immediatetly, which then first the fetchrequest which blocks everything as it is on the main thread.
SOLVED:
Added a boolean:
var loadData = false
override func viewDidAppear(_ animated: Bool) {
loadData = true
self.tableView.reloadData()
}
Then the delegates:
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
if loadData == false {
return nil
}
return searchFetchedResultsController.sectionIndexTitles
}
func numberOfSections(in tableView: UITableView) -> Int {
if loadData == false {
return 0
}
Now it will transition to the new controller immediately and thus I can now show feedback from viewWillAppear :
appDelegate.progressView = ProgressView(Message: "Filtering Brands", Theme:.Dark, IsModal:true);
appDelegate.progressView!.show()
This will NOT work with programatical display
self.navigationController?.pushViewController(vc, animated: false)
Related
If I have two ViewControllers one which contains a UITableView and another which updates data in the tableView. How would I reload the table data once I pop of the viewController and go back to the view with the tableView?
I already tried using viewDidAppear
You could use viewWillAppear just like Rajesh suggested:
override func viewWillAppear(_ animated: Bool) {
tableView.reloadData()
}
Or you could use a callback function to pass data and reload view controller 1's tableview.
In ViewController 2, define your callback function:
// Callback function
var callbackResult: ((data) -> ())?
And call it before going back to ViewController 1:
callbackResult?(data)
self.navigationController?.popViewController(animated: true)
In ViewController 1, use the callback function's closure to collect the result and reload your tableView. This can happen inside prepareForSegue, for example:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToViewController2" {
let destinationVC = segue.destination as! ViewController2
// Set any variable in ViewController2
destinationVC.callbackResult = { result in
// assign passing data etc..
self.tableView.reloadData()
}
}
}
You may try doing something like this:
class TableViewController: UITableViewController {
func showUpdatingViewController() {
let vc = UpdatingViewController()
vc.onUpdate = { [weak self] in
self?.tableView.reloadData()
}
navigationController?.pushViewController(vc, animated: true)
}
}
class UpdatingViewController: UIViewController {
var onUpdate: (() -> Void)?
func updatesFinished() {
onUpdate?()
dismiss(animated: true, completion: nil)
}
}
I was having similar issue and using viewWillAppear or viewDidAppear did not help me to reload the tableview.
I solved my situation by putting the reloadData() call within the unwindSegue
#IBAction func unwindToVCSetupDataScreen(_ unwindSegue: UIStoryboardSegue) {
/// Nothing is actually needed here
/// https://www.youtube.com/watch?v=WaSlHXNah7E #6:25
/// CTRL-Drag from Back button to the "exit" square at the very top of VC
tableView.reloadData()
}
I've spent the whole day trying to figure this out, would like some insight into what's happening here:
I have a custom UITableViewController that's embedded in a navigation controller, and it has a button that triggers a segue to a plain UIViewController.
The plain UIViewController has a button that unwinds to the TableViewController, as well as a "back" button that's given for free thanks to navigation controller.
Now the issue: When I segue from TableViewController to UIViewController, everything works. From UIViewController, if I press the "back" button, it winds back and the TableView is reloaded automatically. HOWEVER, when I press the button that is linked to #IBAction to unwind, it unwinds to the TableViewController but nothing shows up, not even an empty table.
My debug view showed that once I tried to unwind using my button (not "back), TableViewController came back on screen but its frame was super small and nothing was in it, so the black background of the navigation controller was showing, thus just a black screen where the table should be.
Can anyone tell me how to manually reinitialize the tableView or reset the TableViewController when I unwind? I tried tableView.reload() in viewDidAppear() and nothing happened, as I suspect the problem is the TableViewController itself not having the original frame as before segueing.
Link to screenshots with descriptions
TableViewController code:
class StatsTableViewController: UITableViewController {
//variable stuff
#IBAction func unwindToRoot(_ sender: UIStoryboardSegue) {
}
override func viewDidLoad() {
super.viewDidLoad()
self.getData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.async {
self.tableView.reloadData()
}
self.getData()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return solves.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! StatsTableViewCell
cell.timeLabel?.text = solves[indexPath.row].0
cell.scrambleLabel?.text = solves[indexPath.row].1
return cell
}
//other stuff
}
Set Delegate Datasource Methods
//Add this two lines in viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
self.getData()
//Add this line
self.UITableViewName.delegate = self
self.UITableViewName.dataSource = self
}
You are using this two methods in statusTableViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SelectionSegue" {
//Your code
}
}
#IBAction func unwindToThisView(sender: UIStoryboardSegue) {
// set Delegate datasource method
}
set outlet for unwindToThisView button to see the images
1.Click exit button for last ViewController
see Presenting Segues outlet for the Back Button
set action for back button
I looked through SO and compiled these methods below but none of them works for me.
I have a TabBarController with 2 tabs. In the Second tab I have a NavigationController > TableViewController > DetailViewController.
In my DetailViewController I have a custom delegate to send some data to the TableViewController when the Back Button is pressed or the view is Swiped to Dismiss (right swipe). I only want the data sent when the Back Button or Swipe to Dismiss is fully finished and not get sent when the tab is switched or if swiping 3/4 of the way but the user decides NOT to complete the back swipe (basically they stay on the same DetailVC scene).
I tried all of these methods below and they either get triggered when the tab is switched to the first tab, when the DetailVC gets pushed on and popped off, or during the 1/2 way Swipe to Dismiss the DetailVC they still run meaning the data should not have been sent.
DetailViewController:
protocol DetailViewDelegate: class {
func sendSomeData(value: Bool)
}
class DetailViewController: UIViewController{
weak var delegate: DetailViewDelegate?
//1. runs when Tab switches, the Back Button is pressed, and Swipe to Dismiss is triggered
override func viewWillDisappear(_ animated : Bool) {
super.viewWillDisappear(animated)
if (self.isMovingFromParentViewController) || (self.isBeingDismissed){
//doesn't run at all
}else{
//runs whenever view is no longer on scene
sendData()
}
}
//2. runs when Tab switches, Back Button is pressed, Swipe to Dismiss is triggered, and when the view is Pushed on AND Popped off
override func didMove(toParentViewController parent: UIViewController?) {
if parent != nil {
sendData()
}else{
//if parent == nil doesn't run at all
}
}
//3. if switching from the second tab it doesn't run but when switching back to the second tab it does run, also runs when view is being Pushed on and Not Popped on
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
sendData()
}
//4. if switching from the second tab it doesn't run but when switching back to the second tab it does run, also runs when view is being Pushed on and Not Popped on
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
sendData()
}
//MARK:- Custom Func
fileprivate func sendData(){
let value = true
delegate?.sendSomeData(value: value)
}
}
TableViewController:
class TableVC: UIViewController, DetailViewDelegate, UITableViewData..., UITableViewDele...{
var setValue = false
func sendSomeData(value: Bool){
//setValue should only update to true if DetailVC's Back Button is pressed or Right Swipe to Dismiss is fully complete
self.setValue = value
}
}
The TableView never has a problem receiving the data. The problem is when I switch tabs (data still gets sent) or a swipe to dismiss on the DetailVC isn't fully completed (data still gets sent).
What's the best way to send the data from the DetailVC but making sure the Back Button is pressed or Right Swipe to Dismiss is fully complete?
You need to use custom back button and a delegate to call the parent.
this is your parent ViewController:
import UIKit
class ViewController: UIViewController, ViewControllerSecondDelegate {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Next" {
let vc = segue.destination as? ViewControllerSecond
vc?.delegate = self
}
}
func secondDelegate() {
print("delegate") //GetData()
}
}
and this is the child view controller, which you want to back from it to your parent:
import UIKit
protocol ViewControllerSecondDelegate {
func secondDelegate()
}
class ViewControllerSecond: UIViewController, UIGestureRecognizerDelegate {
var isTouched = false
var isPopTouch = true
var delegate: ViewControllerSecondDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.setNavigationBarHidden(false, animated:false)
let myBackButton:UIButton = UIButton.init(type: .custom)
myBackButton.addTarget(self, action: #selector(ViewControllerSecond.popToRoot(sender:)), for: .touchUpInside)
myBackButton.setTitle("Back", for: .normal)
myBackButton.setTitleColor(.blue, for: .normal)
myBackButton.sizeToFit()
let myCustomBackButtonItem:UIBarButtonItem = UIBarButtonItem(customView: myBackButton)
self.navigationItem.leftBarButtonItem = myCustomBackButtonItem
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if isTouched {
isPopTouch = true
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.isPopTouch = false
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if isPopTouch {
delegate?.secondDelegate()
}
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == self.navigationController?.interactivePopGestureRecognizer {
self.isTouched = true
}
return true
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.isTouched = false
}
func popToRoot(sender:UIBarButtonItem){
delegate?.secondDelegate()
self.navigationController?.popToRootViewController(animated: true)
}
}
the above code, handle back button and back gesture.
These are my view controllers:
I want to make it so that I can swipe between the three of them starting in the middle one. All of the tutorials I've found require you to start from scratch and don't show me how to connect the three I have. Does anyone have a step-by-step instruction of how to do this?
Any help would be greatly appreciated
First Create a class called PageViewController, drag a UIPageViewController in storyboard.For now lets set it as initial view controller from attributes inspector. Also from identity inspector set PageViewController as Class.
Call your three view controller for example StepZero,StepOne,StepTwo Also give them identifier in storyboard.
lets deep into coding now, so in PageViewController should subclass UIPageVIewController:
import UIKit
class PageViewController : UIPageViewController,UIPageViewControllerDataSource {
var selectedIndex = 1
override func viewDidLoad() {
dataSource = self
view.backgroundColor = UIColor.darkGrayColor()
// This is the starting point. Start with step zero.
setViewControllers([getStepOne()], direction: .Forward, animated: false, completion: nil)
}
func getStepZero() -> StepZero {
return storyboard!.instantiateViewControllerWithIdentifier("StepZero") as! StepZero
}
func getStepOne() -> StepOne {
return storyboard!.instantiateViewControllerWithIdentifier("StepOne") as! StepOne
}
func getStepTwo() -> StepTwo {
return storyboard!.instantiateViewControllerWithIdentifier("StepTwo") as! StepTwo
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
if viewController.isKindOfClass(StepTwo) {
// 2 -> 1
return getStepOne()
} else if viewController.isKindOfClass(StepOne) {
// 1 -> 0
return getStepZero()
} else {
// 0 -> end of the road
return nil
}
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
if viewController.isKindOfClass(StepZero) {
// 0 -> 1
return getStepOne()
} else if viewController.isKindOfClass(StepOne) {
// 1 -> 2
return getStepTwo()
} else {
// 2 -> end of the road
return nil
}
}
// Enables pagination dots
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return 3
}
// This only gets called once, when setViewControllers is called
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return selectedIndex
}
}
Let's say in Storyboard you have three viewControllers, you should set identifier for them from identity inspector as StepZero StepOne StepTwo for example and when you instantiate them you do :
func getStepZero() -> StepZero {
return storyboard!.instantiateViewControllerWithIdentifier("StepZero") as! StepZero
}
func getStepOne() -> StepOne {
return storyboard!.instantiateViewControllerWithIdentifier("StepOne") as! StepOne
}
func getStepTwo() -> StepTwo {
return storyboard!.instantiateViewControllerWithIdentifier("StepTwo") as! StepTwo
}
The selected index is the index you want to start with which is number 1. And to start with second view controller call getStepOne() in setViewControllers. if you want to start with view controller 3 use selected index 2 and call getStepTwo()...etc
Download Updated Sample : https://mega.nz/#!EQEFhbwS!0yoy5RvAliQNnjRevWo05wPWk7P08e8DVetRZdjg-ro
You can connect your view controllers by navigation controller . Just select one of your view controllers -> Editor(on the top bar of mac) -> Embed in -> Navigation controller.
Also if you want to swipe you can use a scroll view and only on view controller . Scroll view with content size of 3 view controllers can help you do the same.
Thank you
Follow as below image
Give storyboard identifier as shown in below image
On button click push new viewcontroller
var viewControllerObj=self.storyboard!.instantiateViewControllerWithIdentifier("your storyboard identifier")
self.navigationController!.pushViewController(viewControllerObj, animated: true)
I'm building an app that asks users to select a location if they don't allow access to their current location using a Modal that Presents Modally as soon as the user clicks 'Deny'. This modal has information displayed as a TableView, and the modal dismisses as soon as the user selects a row. I save this selection in a variable called selectedStop. I want the app to pause until the user selects a location, then as soon as the user selects a location, the app continues and the setUpMap() function executes. I've tried using an infinite while loop in setUpMap() and using a boolean to break out of it as soon as a user selects a row, but the while loop executes before the Modal even pops up.
ViewController.swift
class ViewController: UIViewController {
var selectedStop: Int!
override func viewDidLoad() {
super.viewDidLoad()
// If we don't have access to the user's current location, request for it
if (CLLocationManager.authorizationStatus() != CLAuthorizationStatus.AuthorizedWhenInUse) {
locationManager.requestWhenInUseAuthorization()
}
}
func setUpMap() {
// do stuff with var selectedStop
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .Denied:
// if user denies access, display modal
self.performSegueWithIdentifier("NotifyModally", sender: self)
setUpMap() // need this func to execute AFTER location is selected
break
case .AuthorizedWhenInUse:
setUpMap()
break
default:
break
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "NotifyModally") {
let destViewController:ModalViewController = segue.destinationViewController as! ModalViewController
// send selectedStop var to ModalViewController
destViewController.selectedStop = selectedStop
}
}
}
ModalViewController.swift
class ModalViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var busStops = ["Stop 1", "Stop 2", "Stop 3"]
var selectedStop: Int!
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return busStops.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel!.text = busStops[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedStop = indexPath.row
dismissViewControllerAnimated(true, completion: nil)
}
}
Using a Int variable to pass information will not working since it's a value type which will get copied every time you pass it around. So that means when you change the selectedStop in the didSelectRowAtIndexPath method, the original selectedStop inside ViewController will still be nil or whatever it was.
And then, to answer your question. There are several ways to solve this.
You can either pass a block (instead an int) to the ModalViewController like this:
var stopSelectedHandler: (Int) -> Void = { selectedStop in
// Do something here.
// setUpMap()
}
You'll call this block inside the completion handler of dismissViewControllerAnimated.
You can use notification.
// Do this inside `ViewController`.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "setupMap:", name: "UserDidSelectStop", object: nil)
// And then post the notification inside `didSelectRowAtIndexPath`
NSNotificationCenter.defaultCenter().postNotificationName("UserDidSelectStop", object: nil, userInfo: ["selectedStop": 2])
// Change your setupMap to this
func setupMap(notification: NSNotification) {
guard let selectedStop = notification.userInfo?["selectedStop"] as? Int else { return }
// Now you can use selectedStop.
}
You can also use KVO, delegate, etc. Use whatever suits you.
Put the block like this:
class ViewController: UIViewController {
var stopSelectedHandler: (Int) -> Void = { selectedStop in
// Do something here.
// setUpMap()
}
....
}