Prefer Large Titles and RefreshControl not working well - swift

I am using this tutorial to implement a pull-to-refresh behavior with the RefreshControl. I am using a Navigation Bar. When using normal titles everything works good. But, when using "Prefer big titles" it doesn't work correctly as you can see in the following videos. Anyone knows why? The only change between videos is the storyboard check on "Prefer Large Titles".

I'm having the same problem, and none of the other answers worked for me.
I realised that changing the table view top constraint from the safe area to the superview fixed that strange spinning bug.
Also, make sure the constant value for this constraint is 0 🤯.

At the end what worked for me was:
In order to fix the RefreshControl progress bar disappearing bug with large titles:
self.extendedLayoutIncludesOpaqueBars = true
In order to fix the list offset after refreshcontrol.endRefreshing():
let top = self.tableView.adjustedContentInset.top
let y = self.refreshControl!.frame.maxY + top
self.tableView.setContentOffset(CGPoint(x: 0, y: -y), animated:true)

If you were using tableView.tableHeaderView = refreshControl or tableView.addSubView(refreshControl) you should try using tableView.refreshControl = refreshControl

It seems there are a lot of different causes that could make this happen, for me I had a TableView embedded within a ViewController. I set the top layout guide of the tableview to the superview with 0. After all of that still nothing until I wrapped my RefreshControl end editing in a delayed block:
DispatchQueue.main.async {
if self.refreshControl.isRefreshing {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
self.refreshControl.endRefreshing()
})
}
}

The only working solution for me is combining Bruno's suggestion with this line of code:
tableView.contentInsetAdjustmentBehavior = .always

I've faced the same problem. Call refreshControl endRefreshing before calling further API.
refreshControl.addTarget(controller, action: #selector(refreshData(_:)), for: .valueChanged)
#objc func refreshData(_ refreshControl: UIRefreshControl) {
refreshControl.endRefreshing()
self.model.loadAPICall {
self.tableView.reloadData()
}
}

The only solution that worked for me using XIBs was Bruno's one:
https://stackoverflow.com/a/54629641/2178888
However I did not want to use a XIB. I struggled a lot trying to make this work by code using AutoLayout.
I finally found a solution that works:
override func loadView() {
super.loadView()
let tableView = UITableView()
//configure tableView
self.view = tableView
}

I had this issue too, and i fixed it by embedded my scrollView (or tableView \ collectionView) inside stackView, and it's important that this stackView's top constraint will not be attached to the safeArea view (all the other constraints can). the top constraint should be connect to it's superview or to other view.

I was facing the same issue for very long, the only working solution for me was adding refresh control to the background view of tableview.
tableView.backgroundView = refreshControl

Short Answer
I fixed this by delaying calling to API until my collection view ends decelerating
Long Answer
I notice that the issue happens when refresh control ends refreshing while the collection view is still moving up to its original position. Therefore, I delay making API call until my collection view stops moving a.k.a ends decelerating. Here's a step by step:
Follow Bruno's suggestion
If you set your navigation bar's translucent value to false (navigationBar.isTranslucent = false), then you will have to set extendedLayoutIncludesOpaqueBars = true on your view controller. Otherwise, skip this.
Delay api call. Since I'm using RxSwift, here's how I do it.
collectionView.rx.didEndDecelerating
.map { [unowned self] _ in self.refreshControl.isRefreshing }
.filter { $0 == true }
.subscribe(onNext: { _ in
// make api call
})
.disposed(by: disposeBag)
After API completes, call to
refreshControl.endRefreshing()
Caveat
Do note that since we delay API call, it means that this whole pull-to-refresh process is not as quick as it could have been done without the delay.

Unfortunately, no advice helped. But I found a solution that helped me. Setting the transparency of the navigation bar helped.enter image description here

Problem can be solved if add tableview or scroll view as root view in UIViewController hierarchy (like in UITableViewController)
override func loadView() {
view = customView
}
where customView is UITableView or UICollectionView

Related

Refresh control target action not triggered

My viewcontroller has a tableview, in which i populate my views in different sections. I am trying to add a refresh control to this tableview. What I have implemented is as below.
func setUpRefreshControl() {
let refreshControl = UIRefreshControl()
refreshControl.tintColor = UIColor.red
refreshControl.addTarget(self, action: #selector(handleRefresh(_: )), for: UIControlEvents.valueChanged)
if #available(iOS 10.0, *) {
tableView.refreshControl = refreshControl
} else {
tableView.addSubview(refreshControl)
}
}
#objc func handleRefresh(_ sender: UIRefreshControl) {
print("-----------REFRESHED------------")
}
Next, when I pull down the tableview to refresh, the refresh control (red in color as setup)is visible. However, the target function is not called.
My viewcontroller is inside a tab bar controller, which is embedded in a navigation controller. I guess the issue is related to view hierarchy because when I try the same code in a separate project with no nav bar and tab bar, its working fine. But I cannot figure out what the current issue is. Any suggestion on solving this? Thank you.
EDIT: I tested it in different simulators: 6, 6s, 6s+, 7, 7+, 8, 8+, X. I found that the above code runs fine in all the plus versions including X. However, all the simulators are running ios 11.2 so I still cannot figure it out what might be causing this issue.
I was having a similar issue, pull to refresh would work fine on 5.5" screens but all other phones would show only a partial animation then never quite trigger the refresh.
I found invoking refreshControl.didMoveToSuperview() in the viewDidAppear for my view controller fixed the problem.
If I understand correctly, this same method is invoked when you set tableView.refreshControl = refreshControl but needs recalculation by the time the view is actually set to appear. I'll need to dig in a bit more to be sure of the specifics, but hope this is helpful for the time being. 🙂
I solved this issue changing the property Size in Simulated Metrics to Inferred and the property Simulated Size to Fixed in the view controller.
I hope this works for you.
This is the refresh control code which i implemented and its working for me in my current project, just 3 days ago.
var refreshControl: UIRefreshControl!
this is my global refreshControl variable and below is the implementation of it.
//MARK: - REFRESH CONTROLLER VIEW
refreshControl = UIRefreshControl()
refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl.addTarget(self, action: #selector(refresh(_ :)), for: UIControlEvents.valueChanged)
refreshControl.attributedTitle = NSAttributedString(string: "")
tblRequest.addSubview(refreshControl)
Hope, this works for you ! :-)

Programmatic beginRefreshing() on iOS11 has problems with largeTitles mode

We have found what seems to be a bug in UIKit but wanted to post here to see if anyone else has this problem or found a solution.
We're trying to use the new iOS11 large titles and hoisted search bar/refreshcontrol. We seemed to have found a problem where the root viewController of the navigation stack shows a minor display issue (problem A) but once another viewcontroller is pushed onto the navigation stack, the display goes nuts (problem B):
Things to note:
The problem is worse on the 2nd VC in the stack rather than the 1st
The refreshControl is not the green color the code sets it to the 1st time you see it on each sceen
The refreshControl slides down as you pull to refresh, it shouldn't do this
This odd behavior seems to only be a problem when we programmatically do a "pull to refresh" in viewDidLoad so that the user can see that the data is loading when they enter the screen. If we remove the lines that invoke refreshControl?.beginRefreshing() the display is clean. I've recreated this problem in a sample vanilla app. This is the entirety of the viewcontroller that shows the problem:
import UIKit
class ViewController: UITableViewController {
var tableHeaderSearchController: UISearchController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationController?.navigationItem.largeTitleDisplayMode = .always
tableHeaderSearchController = UISearchController(searchResultsController: UITableViewController())
navigationItem.searchController = tableHeaderSearchController
refreshControl?.tintColor = UIColor.green
refreshControl?.backgroundColor = UIColor.clear
refreshControl?.attributedTitle = NSAttributedString(string: "Loading Stuff...", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 17)])
refreshControl?.addTarget(self, action: #selector(refreshPulled), for: .valueChanged)
// Commenting out these 2 lines makes it work fine but you can't see the initial refresh spinner
refreshControl?.beginRefreshing()
refreshPulled()
}
#objc func refreshPulled() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [refreshControl] in
refreshControl?.endRefreshing()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Here's the storyboard. It's just a vanilla tableviewcontroller wrapped in a navigationController. 3 static cells, the 2nd one traverses to another instance of the same controller type.
Any ideas would be greatly appreciated. We'd really like to adopt the new look but this stuff is making it very hard to do so.
First, it is absolutely crucial that the table view extend up underneath the navigation bar and that is iOS 11 offset behavior be correct:
self.edgesForExtendedLayout = .all
self.tableView.contentInsetAdjustmentBehavior = .always
Second, scrolling to show the refresh control when you refresh manually is up to you, and calculating the amount is not at all simple:
self.refreshControl!.sizeToFit()
let top = self.tableView.adjustedContentInset.top
let y = self.refreshControl!.frame.maxY + top
self.tableView.setContentOffset(CGPoint(0, -y), animated:true)
self.refreshControl!.beginRefreshing()
The bar still stays too big during the refresh, but I don't see what can be done about that. Basically Apple has implemented large titles and shown the refresh control in the nav bar without thinking through the effects or dealing with the resulting bugs.

How to Disable Multi Touch on UIBarButtonItems & UI Buttons?

My app has quite a few buttons on each screen as well as a UIBarButtonItem back button and I have problems with people being able to multi click buttons. I need only 1 button to be clickable at a time.
Does anyone know how to make a UIBarButtonItem back button exclusive to touch?
I've managed to disable multi clicking the UIButtons by setting each one's view to isExclusiveTouch = true but this doesn't seem to count for the back button in the navigation bar.
The back button doesn't seem to adhere to isExclusiveTouch.
Does anyone have a simple work around that doesn't involve coding each and every buttons send events?
Many Thanks,
Krivvenz.
you can enable exclusive touch simply this will stop multiple touch until first touch is not done
buttton.exclusiveTouch = true
You could write an extension for UIBarButtonItem to add isExclusiveTouch?
you can simply disable the multi-touch property of the super view. You can also find this property in the storyboard.
you could try this for the scene where you want to disable multiple touch.
let skView = self.view as! SKView
skView.isMultipleTouchEnabled = false
I have found a solution to this. isExclusiveTouch is the solution in the end, but the reason why this property didn't do anything is because you need to set it also on all of the subviews of each button that you want to set as isExclusiveTouch = true. Then it works as expected :)
It's working fine in Swift
self.view.isMultipleTouchEnabled = false
buttonHistory.isExclusiveTouch = true
In addition of #Luky Lízal answer, you need pay attention that all subviews of a view you want disable multi-touching (exactly all in hierarchy, actually subviews of subviews) must be set as isExclusiveTouch = true.
You can run through all of them recursively like that:
extension UIView
{
func allSubViews() -> [UIView] {
var all: [UIView] = []
func getSubview(view: UIView) {
all.append(view)
guard view.subviews.count > 0 else { return }
view.subviews.forEach{ getSubview(view: $0) }
}
getSubview(view: self)
return all
}
}
// Call this method when all views in your parent view were set
func disableMultipleTouching() {
self.isMultipleTouchEnabled = false
self.allSubViews().forEach { $0.isExclusiveTouch = true }
}

Swift tableView bottom loading indicator

I am currently struggling with implementing a bottom loading indicator for my app, exactly like Instagram and Facebook has. Simply I want to show a loading indicator at the bottom (on reverse drag) just like a normal table view loading.
Here is the code that I have for the regular table view update:
var refreshControl: UIRefreshControl!
//In viewDidLoad
refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: "Refresh:", forControlEvents: UIControlEvents.ValueChanged)
tableView.addSubview(refreshControl)
In my func Refresh() I simply just fetch the data, and controls the activity indicator from there. However, how would I approach this, if I wanted to enable this in the bottom of my tableView?
Help is much appreciated.
Add it to the table footer view
tableView.tableFooterView = footerView
Adding a refreshControl would be difficult.
Add a UIIndicatorView to the footerview.
Implement scrollViewDidScroll:
func scrollViewDidScroll(scrollView: UIScrollView) {
if (scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height {
tableView.tableFooterView!.hidden = true
// call method to add data to tableView
}
}
Refresh Controller is for pulltorefresh which usually use to reload data.
Spinner at the bottom of the screen is used for pagination.
What you need to do in numberOfRows method return arraysize+1.
in cellforRowAtIndexPath method check if indexpath.row > arraySize than return a cell having uiactivitycenter in the center.
Hope this will help you.
If you are getting the data from API call with pagination, which has to show in table view Then Kuntal's answer is right, in addition you can make 0 hight for cell containing indicator view when there is no more data, and after completion of reload. It is easy manage than check and return arraysize+1.

How to dismiss keyboard in UISearchController when changing focus in tvos?

I'm completely new to tvos and I'm trying to implement a UISearchController view where, in my SearchResultsViewController, I have two UICollectionViews displayed one above the other:
The problem is that when the user swipes down to select one of the items in the UICollectionView, the keyboard doesn't dismiss. Even swiping back up to select the keyboard doesn't fully scroll up and it's impossible to see what you're typing. The resulting view is this:
Ideally, I'd like to dismiss the keyboard when the user swipes down to focus on anything else in the interface. I looked at Apple's tvos UIKit Catalog and their example shows a UISearchController which dismisses the keyboard when changing focus, but I don't see that they're doing anything differently.
Here is the code I'm using to setup my UISearchController when the user clicks on a button:
#IBAction func onSearchButton(sender: AnyObject) {
guard let resultsController = storyboard?.instantiateViewControllerWithIdentifier(SearchResultsViewController.storyboardID) as? SearchResultsViewController else { fatalError("Unable to instantiate a SearchResultsViewController.") }
// Create and configure a `UISearchController`.
let searchController = UISearchController(searchResultsController: resultsController)
searchController.searchResultsUpdater = resultsController
searchController.hidesNavigationBarDuringPresentation = false
let searchPlaceholderText = NSLocalizedString("Search for a Show or Movie", comment: "")
searchController.searchBar.placeholder = searchPlaceholderText
// Present the search controller from the root view controller.
guard let rootViewController = view.window?.rootViewController else { fatalError("Unable to get root view controller.") }
rootViewController.presentViewController(searchController, animated: true, completion: nil)
}
After quite a bit of trial and error, I was able to figure out the solution.
The keyboard will automatically dismiss itself as long as:
1) The item the user focuses on is inside of a scrollview
2) The scrollview content size is larger than the screen height by at least 1px (1081px).
After quite a lot of trial and error, finally I figured out.
The reason is that you have nested ScrollViews in searchResultsController.
"ScrollViews" of-course includes UICollectionView, UITableView, and UIScrollView.
According to my investigation, UISearchController behaves as follows.
If the first view which gets focused in searchResultsController is subview of the inner scrollView (which is the horizontal UICollectionView, in your case), then you won't get keyboard hidden as expected.
Interestingly, if the first view which gets focused in searchResultsController is subview of outer scrollView, then you will get keyboard hidden completely, animated, just as expected (!).
I think this is sort of UIKit's bug.
I had exactly same layout and wasn't able to achieve this so far. I believe you return false in tableView(tableView: UITableView, canFocusRowAtIndexPath indexPath: NSIndexPath) -> Bool so that each cells in collection view can scroll horizontally with proper focus behavior. I think it's actually causing the issues. If you make the first cell in the tableview focusable the problem goes away but of course focus behavior is not desired. I just found that out today and will try more tomorrow to find out what I can do about this. I sense that I will need a new design that allows me to use a single collectionview or tableview that has its cells focusable in resultsController. Hope this is easily achievable in tvOS 10.
Work Around Solution: Add one dummy cell at indexPath.row == 0 with height as 1 pixel and enable the focus on it.