UIRefreshControl is flickering and transitions are abrupt - ios

I'd like to set up a UIRefreshControl in my apps' main table view controller to trigger a refresh function to (you probably guessed it by now) refresh it.
Adding it isn't the problem, I'm able to set it up through the storyboard (by enabling refreshing) or in the controller (by using self.refreshControl = UIRefreshControl()).
When it gets triggered by the user it starts refreshing normally, but when I trigger it through self.refreshControl.beginRefreshing() the control isn't displayed in the navigation bar, until I scroll down (like you would normally trigger it)
But that's not even the weirdest part. When it's finally visible to the user there is a constant flicker to it and after a while it's not visible at all.
For the self.refreshControl.endRefreshing() method, the animation is really abrupt any choppy and sometimes the navigation bar doesn't scroll up again (leaving an empty space at the top)
Here is a gif, that should summarise my problem
I use swift 4.2, the application was tested on different simulators (running iOS 12.1) and my iPhone X (running iOS 12.1.2) and the release target is iOS 10.0.
I've already done research on my problem, but no one seems to have the exact issue (at least the flickering) or the solutions don't work for me.
This is how my view controller is set up:
class MyViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.refreshControl = UIRefreshControl()
self.refreshControl?.addTarget(self, action: #selector(reload), for: .valueChanged)
self.refreshControl.beginRefreshing()
}
#objc func reload() {
DispatchQueue(label: "update").async {
sleep(10)
DispatchQueue.main.async {
self.refreshControl?.endRefreshing()
}
}
}
}
Thanks for reading, I really hope someone can help me.
Update
I just discovered that the issue only occurs with a non-translucent navigation bar with large title, so picking a translucent navigation bar instead of the opaque is a solution (sadly not one I would like to live with). Maybe someone has an approach to having a functional refresh control with an opaque navigation bar.
Additionally I submitted a bug report to Apple, because something like that should not happen in the first place, I'll update this post when they get back to me.

Related

UISplitViewController - replace back-chevron icon with a sidebar toggle icon in portrait

I'm working on an iPadOS app in which I use UISplitViewController. So far I have been using old APIs and handling everything (including the displayModeButtonItem) manually. Now I wanted to migrate to the newer "column style" APIs (super.init(style: .doubleColumn). I had a few UI issues, but I managed to fix them with a few workarounds, but now I'm blocked on an issue for which I can't find any solution:
I'd like to make use of all built-in mechanisms of the iOS14's UISplitViewController, so I set presentsWithGesture property to true. Because of this I get sidebar toggle icon in landscape orientation (which works exactly how I want it), but in portrait I get a "back-chevron icon" with "back" title. Is there a way to force sidebar toggle icon to be displayed for both orientations?
I set up a sample project and I'm not getting the behavior you are. I'm thinking it's because we do things differently in setting up the UISplitViewController.
First, a major thanks to Matt Neuburg (#matt here on SO) for his two part article. (part one here) This provided me with some big changes to my code.
Basically, I no longer set my UISplitViewController to be the root view in my scenes. Instead, I do not touch SceneDelegate at all, but use the default ViewController:
let primaryVC = PrimaryVC()
let secondaryVC = SecondaryVC()
let compactVC = CompactVC()
override func viewDidLoad() {
super.viewDidLoad()
let split = UISplitViewController(style: .doubleColumn)
self.addChild(split)
self.view.addSubview(split.view)
split.view.frame = self.view.bounds
split.presentsWithGesture = true
split.preferredSplitBehavior = .tile
split.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
split.didMove(toParent: self)
split.setViewController(primaryVC, for: .primary)
split.setViewController(secondaryVC, for: .secondary)
split.setViewController(compactVC, for: .compact)
}
When I do this, I get a "sidebar.left" button and no "Back" button with Chevron. I'm using Xcode 13.1 and tried this with targets of iPadOS 14.0 and 15.1.
If you need to add/replace a bar button, what I do is:
var barBtnSidebarLeft = UIBarButtonItem(image: UIImage(systemName:"sidebar.left"), style: .plain, target: self, action: #selector(leftSidebarTapped))
navigationItem.leftBarButtonItem = barBtnSidebarLeft
But beware that if you do this, you will get two sidebar buttons! With regards to bar buttons, one more note - while the UISplitViewController provides a navigation bar by default for Primary and Secondary, it does not for Compact. (Frankly, I don't think it's good practice to have one when in compact size.)
Finally, read through the link I provided. It really gets into using UISplitViewControllerDelegate to help you pass along your scene state values properly. (I prefer calling it scene state instead of app state because iPads can have multiple app windows.)
EDIT
One last thought. IIRC you can do this:
set presentsWithGesture to `false
place your own bar buttons as pleased in both view controllers
manually (through the delegate functions?) open and close the Primary view controller

UIRefreshControl not showing in landscape when in a navigation controller with large titles

I have a plain UITableViewController embedded in a UINavigationController which prefers large titles. I added a refresh control to my table view controller.
On iPhone in iOS 11, if I launch my app in portrait mode, switch to landscape and begin refreshing the table view, the refresh control does not show up. The refresh happens, but the control is just not present. It's not even in the view hierarchy:
Note that the same does work on iPhone if I'm in portrait mode, and also on iPad in every orientation. It also works on iOS 10 with every device and every orientation and also works on iOS 11 if I disable large titles, so it obviously has something to do with the large titles.
Here is a screenshot from the same thing on iOS 10:
I made a sample project for this. There is no code involved, here is the whole setup in Interface Builder:
The same thing happens when I try it with a simple UIViewController with an embedded UITableView or UIScrollView.
I'm pretty sure this is a bug. How can I make the refresh control visible on iPhone in landscape?
We fixed this by recreating the refresh control on every rotation. It's called in viewDidLoad() too.
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.verticalSizeClass != previousTraitCollection?.verticalSizeClass {
tableView.refreshControl = UIRefreshControl() // !!!
tableView.refreshControl.addTarget(self, action: #selector(didPullToRefresh), for: .valueChanged)
}
}
#objc func didPullToRefresh() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
tableView.refreshControl.endRefreshing()
}
// ...
}
This can clearly be characterized as a bug. It's readily reproduced. Your description seems quite correct; once you've exposed the refresh control in portrait and then rotated to landscape, the refresh control isn't even in the interface in landscape (that's easy to see if you give it a background color).
On the whole, however, this is not very surprising. There are many bugs connected with large titles; Apple clearly hasn't thought through this feature very carefully.

Navigationbar change (sometimes) position

I have problems with the position of my navigation bar.
Sometimes in random views on random phones my navigation bar hasn't the right position. It should be approximately 30-35 pixels lower.
The stupid thing is that I can't reproduce the bug. It seems that the bug is appearing when the phone have a lot of processes in the background.
The logic behind: My initial view is a Container View. This Container View implement a Navigation View Controller. I can't change this, because I undertook this project. If it does not have to be, I don't want to change the basic structure.
I know for you it is like a clairvoyant, because I can't give more information. I wish I could...
But maybe you know how to set the navigation in a fix position? Or you know this kind of bug and fixed it already?
I am thankful for some ideas.
Update
Thanks for reply.
I implement this code, I can't say, whether it's working, because my bug doesn't appear always. We will see and I will give reply as fast as possible.
func fixPositionOfNavigationBar() {
self.edgesForExtendedLayout = .Top
self.extendedLayoutIncludesOpaqueBars = true
if let navigationBar = self.navigationController?.navigationBar {
positionForBar(navigationBar)
}
}
When I know for what I am searching I found similar problems to mine.. maybe it helps:
One possible solution
Snd possible solution
Try conforming view controller to UIBarPositioningDelegate protocol by this:
func positionForBar(bar: UIBarPositioning) -> UIBarPosition {
return UIBarPosition.TopAttached
}

Delay when using instantiateViewControllerWithIdentifier but not performSegueWithIdentifier?

The code below is used to push another view controller onto the navigation stack.
When using instantiateViewControllerWithIdentifier, the segue is noticeably sluggish the first time (~3 seconds) but occurs reasonably fast each subsequent time. Other SO posts suggested ensuring the segue occurs on the main thread, which the code accomplishes, but this didn't fix the problem.
However, using performSegueWithIdentifier causes no delay.
The viewDidLoad code for SendViewController is the same for the first and subsequent pushes.
Tried blanking out viewDidLoad for the destination view controller, but still the lag exists for instantiateViewControllerWithIdentifier but not for performSegueWithIdentifier.
How to fix the delay with instantiateViewControllerWithIdentifier?
No delay:
#IBAction func buttonTapped(sender: UIButton) {
performSegueWithIdentifier(SendSegue, sender: self)
}
Results in delay when showing SendViewController for first time:
#IBAction func buttonTapped(sender: UIButton) {
dispatch_async(dispatch_get_main_queue()) {
let vc = self.storyboard!.instantiateViewControllerWithIdentifier(self.SendViewControllerID) as! SendViewController
self.navigationController!.pushViewController(vc, animated: true)
}
}
This issue could occur in many different scenarios. The best way determine what is causing your specific problem is by profiling with the instruments included in Xcode.
Click and hold the Build button in your xcode window. You will see four options appear, select Profile.
Once the build runs a window with instruments will pop up. Select, Time Profiling from the options.
A new window will appear with various metrics in it. The top left corner will have a red record button. Click the red record button and this will launch the app on your phone.
Proceed to the transition giving you problems. End the recording after the transition occurs by selecting the same button you started the recording with.
Review the "Details" pane in the bottom left corner. You will see a column titled "Running time" that shows the time it took to execute every method in your code (both OS methods and user generated code)
Determine if anything is out of place or occurs that is not intended. Possibly go back and execute the transition again to compare the difference between the two. Clicking the function in the list will take you directly to the code being executed. This can be very helpful.
It is very likely that if a transition takes 3-5 seconds one particular function will be obvious when following these steps. Happy profiling!
WWDC from last year has a great segment on this as well. Def worth checking out here: (open in Safari only) WWDC Profiling Talk
The problem was isolated to the presence of a UITextField in the destination view controller, that is, removing the UITextField removes the lag.
Then it was further isolated to the presence of a custom font.
In other words, using the system font on the UITextField, rather than a custom font, removes the lag. No explanation why, but it works.
After time profiling I realized it was the call to instantiateViewController which I couldn't find anything that could help me with that.
Unfortunately, the only thing that worked was either using a separate storyboard for that view controller and instantiating it from there, or redoing the view controller programmatically.

How to restart UIRefreshControl animation?

I have several tableviews in a tab bar controller. On some of these I have set a UIRefreshControl.
After swiping down to start a refresh the 'spinner' animation begins. If I select a different tab and then move back to the original tab, the refresh control is still shown but the animation has stopped.
How do I restart this animation?
I've tried starting the refresh again and got no results.
I also inspected the view hierarchy, hoping to find a UIActivityIndicatorView but could only find a _UIRefreshControlModernReplicatorView that seems to be the view doing the animation. I don't know of any method I can call of this to get the animation restarted.
I had a similar problem, although not exactly the same problem as described in the original question.
The issue: When I moved from a screen with a tableView with refresh control (not a UITableViewController) to a different one, then I found when I returned to the screen, the activity indicator portion of the refresh control was displaying (not animating) as an artifact just under the top of the table view.
I tried a lot of ideas (hiding the refresh control, ensuring endRefreshing was called, etc.) until I found something that actually worked:
In viewWillAppear(animated:), add the following:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.refreshControl = nil
tableView.refreshControl = refresher
}
Just adding that code fixed the issue for me. Your mileage may vary, but perhaps somebody will find this useful.

Resources