Dismiss SVProgressHUD if exiting View Controller - ios

I have a view controller that on launch shows an SVProgressHUD as data is fetched from the server. How would I dismiss the SVProgressHUD if moving back to the previous view controller before the fetch is completed?
I have tried this:
override func viewWillDisappear(animated : Bool) {
super.viewWillDisappear(animated)
if (self.isMovingFromParentViewController()){
print("before dismiss")
SVProgressHUD.dismiss()
print("after dismiss")
}
}
I get the error: fatal error: unexpectedly found nil while unwrapping an Optional value
Here is some code from the view controller that is being navigated back to:
override func viewWillAppear(animated: Bool) {
self.navigationItem.hidesBackButton = true
}
override func viewDidLoad() {
searchRestaurants.delegate = self
loadRestaurants()
super.viewDidLoad()
// Do any additional setup after loading the view.
//navigation bar title edit
self.navigationController!.navigationBar.titleTextAttributes = [ NSFontAttributeName: UIFont(name: "Chalkboard SE", size: 17)!]
//fetch list of restaurant names from database
//set table cells to values inside array
}
override func viewDidAppear(animated: Bool) {
//SVProgressHUD.dismiss()
}
override func viewWillDisappear(animated : Bool) {
super.viewWillDisappear(animated)
if (self.isMovingFromParentViewController()){
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
print("This is run on the background queue")
SVProgressHUD.dismiss()
})
}
}

Related

Pull to refresh and activity indicator on a view

I've a VC containing Table View. I've implemented pull to refresh on table view and activity indicator on view. On pull to refresh table view flickers as it looks like it has finished refreshing and reloads while I pull down and keep holding the table view. I'm not sure where it's going wrong. Following is my code. How to I implement it correctly?
class MyViewController: CustomVC {
override func viewDidLoad() {
super.viewDidLoad()
self.addRefreshControl(to: tableView)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.view.activityStartAnimating()
self.tableView.isHidden = true
loadContent(isRefresh: false)
}
private func loadContent(isRefresh: Bool) {
fetchContent() { (result, error) in
// ....
if isRefresh {
self.refreshControl.endRefreshing()
} else {
self.view.activityStopAnimating()
}
// ...
self.tableView.reloadData()
self.tableView.isHidden = false
}
}
// Pull to refresh
override func fetchDataForRefresh() {
loadContent(isRefresh: true)
}
}
Custom VC class
class CustomVC: UIViewController {
lazy var refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(refresh(_:)), for: UIControl.Event.valueChanged)
refreshControl.transform = CGAffineTransform(scaleX: 0.87, y: 0.87)
return refreshControl
}()
override func viewDidLoad() {
super.viewDidLoad()
}
func addRefreshControl(to tableView: UITableView) {
if #available(iOS 10.0, *) {
tableView.refreshControl = refreshControl
} else {
tableView.addSubview(refreshControl)
}
tableView.indicatorStyle = .white
}
#objc func refresh(_ refreshControl: UIRefreshControl) {
fetchDataForRefresh()
}
func fetchDataForRefresh() {
}
}
Fetch Content
func fetchContent() {
// .....
DispatchQueue.main.async {
completed(resultData,nil)
return
}
//....
}
Try this technique
func setupRefreshControl() {
refreshControl = UIRefreshControl()
refreshControl?.addTarget(self, action: #selector(refreshData), for: .valueChanged)
tableView?.refreshControl = refreshControl
}
When you finished fetching your data:
DispatchQueue.main.async {
self.refreshControl?.perform(#selector(UIRefreshControl.endRefreshing), with: nil, afterDelay: 0)
self.tableView.reloadData()
}
Please note I have access to refreshControl because I'm inside a UITableViewController but you can easily add it in a UIViewController as well.
Remove the if refresh else ... from your code and put your reload your UITableView and endRefreshing on the main thread as I mentioned above

Enabling a disabled tab

I have a UITabBar containing 5 tabs. I disabled one of the tabs like this:
tabBar.items?[3].isEnabled = false
To enable it again, I am using the following code:
tabBar.items?[3].isEnabled = true
The problem is that it doesn't actually get enabled again. I also tried to place the above code inside viewWillAppear and viewDidAppear, but the tab stays disabled.
Here's the full code:
import UIKit
class MainTabViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
tabBar.items?[0].title = NSLocalizedString("tab1", comment: "-")
tabBar.items?[1].title = NSLocalizedString("tab2", comment: "-")
tabBar.items?[2].title = NSLocalizedString("tab3", comment: "-")
tabBar.items?[3].title = NSLocalizedString("tab4", comment: "-")
tabBar.items?[4].title = NSLocalizedString("tab5", comment: "-")
self.tabBar.items?[3].isEnabled = true
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
}
It should be the tabBar property of the UITabBarController.
self.tabBarController?.tabBar.items?[3].isEnabled = false
If you call it from inside the custom UITabBarController subclass:
self.tabBar.items?[3].isEnabled = false
Make sure viewWillAppear and viewDidAppear of the custom UITabBarController subclass are called only once, unlike the methods in every tab, since they are called every time the tab is selected.
The below code seems to work fine:
self.tabBar.items?[3].isEnabled = false
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.tabBar.items?[3].isEnabled = true
}

Large UINavigationBar resize smoothly when popping back too rootViewController

hey guys I've used large UINavigationBar in a child ViewController and i want to resize my navBar to default size when popping back to rootViewController smoothly.
vc's gif:https://giphy.com/gifs/1P0HwqlIqqMnzibxbH
EDIT
I don't want to remove largeNavBar from parent vc, i only want to disappear it gradually and with animation like app store:https://giphy.com/gifs/YXsTA6I5r0lGik1gC8
here is the child vc code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.initUI()
super.enableLargeNavigationTitle(title: (self.favorty?.sellerProduct?.product?.name)!)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
super.removeTitleImage()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(true)
}
here is the enableLargeNavigationBar function:
func enableLargeNavigationTitle(title: String) {
self.navigationController?.view.backgroundColor = VVUtility.splashBackGroundColor()
self.navigationItem.title = "\(title)".localized()
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor : UIColor.white, NSAttributedStringKey.font : VVUtility.normalFontWithPlusSize(increaseSize: -2.0)]
if #available(iOS 11.0, *) {
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationController?.navigationBar.backgroundColor = VVUtility.splashBackGroundColor()
self.navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedStringKey.foregroundColor : UIColor.white, NSAttributedStringKey.font : VVUtility.normalFontWithPlusSize(increaseSize: 0.0)]
} else {
// Fallback on earlier versions
}
}
disableLargeNavigation function:
func disableLargeNavigationTitle() {
if #available(iOS 11.0, *) {
self.navigationController?.navigationItem.largeTitleDisplayMode = .never
self.navigationController?.navigationBar.prefersLargeTitles = false
} else {
// Fallback on earlier versions
}
}
here is parent vc code:
override func viewDidLoad() {
super.viewDidLoad()
self.initUI()
self.getData()
super.disableLargeNavigationTitle()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.addSubview(searchBarBoxView)
self.timerDelegate?.startTimer()
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.searchBarBoxView.removeFromSuperview()
self.timerDelegate?.stopTimer()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.tabBarController?.delegate = self
super.disableLargeNavigationTitle()
}
This worked for me. Try putting this code in awakeFromNib() for each view controller, with the settings changed as you need.
override func awakeFromNib() {
// Large titles
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = false // This could be true for other view controller
navigationItem.largeTitleDisplayMode = .never // This could be .always for other view controller
navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.black] // Or whatever you want
} else {
// Handle iOS 10 and below (no large titles)
}
}
Alternatively, I think you can do this just in Storyboard, but that didn’t work for me.

SubView(nib) wont remove after calling removeFromSuperView()

I have an overlay view to segregate content, I'm checking for authentication in viewWillAppear() and I have a Notification subscribed to my Auth method. If I authenticate before any of my other views appear the overlay does not show up, however it does on the first view and will not go away even after calling removeFromSuperView().
import UIKit
import FirebaseAuth
class ProtectedViewController: UIViewController, ForceSignInBannerDelegate,
SignUpViewControllerDelegate, LoginViewControllerDelegate{
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
NotificationCenter.default.addObserver(self, selector: #selector(checkAuthentication), name: .myNotification, object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.checkAuthentication()
}
func checkAuthentication() {
let bannerViewController = ForceSignInBanner.instanceFromNib() as! ForceSignInBanner
bannerViewController.delegate = self
if (!AuthenticationService.sharedInstance.isAuthenticated()) {
self.setView(view: bannerViewController, hidden: false)
print("Need to login")
} else if(AuthenticationService.sharedInstance.isAuthenticated()) {
self.setView(view: bannerViewController, hidden: true)
}
}
func setView(view: UIView, hidden: Bool) {
UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: { _ in
view.isHidden = hidden
if hidden {
view.removeFromSuperview()
} else {
self.view.addSubview(view)
}
}, completion: nil)
}
It's because you're trying to remove a new ForceSignInBanner each time. Ideally you should create it once and keep a reference to the ForceSignInBanner created (as an optional property of ProtectedViewController).
Then remove the ForceSignInBanner that you've stored in the property.
class ProtectedViewController: UIViewController, ForceSignInBannerDelegate {
// This lazily loads the view when the property is first used and sets the delegate.
// Ideally you wouldn't force-case the `as` but I've left it for simplicity here.
private lazy var forceSignInBannerView: ForceSignInBanner = {
let forceSignInBannerView = ForceSignInBanner.instanceFromNib() as! ForceSignInBanner
forceSignInBannerView.delegate = self
return forceSignInBannerView
}()
// ... your other code ... //
fun toggleForceSignInBannerViewVisibility(isVisible: Bool) {
if isVisible {
view.addSubview(forceSignInBannerView)
} else {
forceSignInBannerView.removeFromSuperview()
}
}
}

UIRefreshControl spins too fast

I want to trigger refreshControl to show automatically when a tableView is initializing its data from server, and I have tried several ways in this question
UIRefreshControl - beginRefreshing not working when UITableViewController is inside UINavigationController
The refreshControl did show when the data is being initialized at the begining, but it spins too fast(maybe 2x the normal speed).
Does anyone know the reason and how to solve this problem?
Here is the code
override func viewDidLoad() {
super.viewDidLoad()
networkManager = BookNetworkManager(userAccount: userAccount)
HUD = JGProgressHUD.standard
refreshControl!.addTarget(self, action: #selector(refreshData), for: .valueChanged)
tableView.tableFooterView = UIView()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
delegate?.setNavgationItem(status: .none)
if !loaded {
tableView.setContentOffset(CGPoint(x: 0, y: -self.refreshControl!.height - self.topLayoutGuide.length), animated: true)
refreshControl!.beginRefreshing()
refreshData()
loaded = true
}
}
func refreshData() {
delegate?.hidePlaceHolder(type: .history)
refreshControl!.attributedTitle = NSAttributedString(string: "刷新数据中")
networkManager.getHistoryBook()
{ (response, errorInfo, books) in
if response == .success {
self.historyBookList = books
self.historyBookList.sort{ $0.returnDateTimeInterval > $1.returnDateTimeInterval }
self.tableView.reloadData()
if books.isEmpty {
self.delegate?.showPlaceHolder(message: "还没有借阅过书籍噢", for: .history)
}else{
self.delegate?.hidePlaceHolder(type: .history)
}
}else {
print(errorInfo?.errorDescription ?? response.rawValue)
self.HUD.show(message: errorInfo?.errorDescription ?? response.rawValue, time: 2)
self.delegate?.showPlaceHolder(message: "出错啦,下拉刷新一下吧?", for: .history)
}
self.refreshControl!.endRefreshing()
self.refreshControl!.attributedTitle = NSAttributedString(string: "下拉刷新数据")
}
}

Resources