Swift UIRefreshControl not working on iPhone 8 - ios

I have an UIRefreshControl which works on every device except iPhone 8 (works neither on a physical device or an emulator).
On an iPhone 8 the refresh indicator stops at 3/4 to the end and never calls refresh
This is how it looks like when you pull it all the way down:
Creating the refresh control:
lazy var refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action:
#selector(TeamViewController.handleRefresh(_:)),
for: UIControlEvents.valueChanged)
refreshControl.tintColor = UIColor.red
return refreshControl
}()
Adding to the view:
if #available(iOS 10.0, *) {
myCollectionView.refreshControl = refreshControl
} else {
myCollectionView.addSubview(refreshControl)
}

I had the same problem and was caused by me setting the view controller to freeform in the storyboard as the simulated size. Changing it back to fixed resolved the issue.
Can also be fixed by using:
override func viewDidLayoutSubviews() {
refreshControl?.didMoveToSuperview()
super.viewDidLayoutSubviews()
}
UIRefreshControl needs to be pulled down too far

Related

iOS 13 UISearchController + UIRefreshControl glitch

I'm using a UISearchController for search in a UITableViewController that also supports pull-to-refresh via UIRefreshControl.
The setup is very simple in a reduced Demo project
override func viewDidLoad() {
extendedLayoutIncludesOpaqueBars = true
title = searchTerm ?? "Search"
super.viewDidLoad()
setupSearch()
setupRefresh()
}
private func setupSearch() {
searchController.searchResultsUpdater = self
navigationItem.searchController = searchController
definesPresentationContext = true
//
// If this is set to `true` (which is also the default),
// UISearchBar and UIRefreshcontroll are buggy
//
navigationItem.hidesSearchBarWhenScrolling = true
}
private func setupRefresh() {
refreshControl = UIRefreshControl()
refreshControl?.addTarget(self, action: #selector(refresh), for: .valueChanged)
}
This worked in iOS 12, but now in iOS 13 (compiled for iOS 13 with Xcode 11 GM), the Refresh Animation is broken
The only "fix" I have found so far is to set navigationItem.hidesSearchBarWhenScrolling to false, but this obviously causes the Searchbar to always stay on screen, even when scrolling.
Here is a sample project demonstrating the issue: https://github.com/iv-mexx/UISearchControl-UIRefreshControl-iOS13-Bug/tree/feature/ios13
Update: This still remains broken in Xcode 11 GM Seed 2
If turning on large titles is an option for your app, this seems to workaround the issue as well.
This might be why Apple isn't running into the issue in their own apps.

UIRefreshControl stucks only in iPhone X/ iPhone XR

I am working on an application where I implement UIRefreshControl. All things going fine but when testing move on iPhone X or iPhone XR, pthen a weird problem raised.
The problem is UIRefreshControl Stucks sometimes when we try to refersh the list. Or we can say it occurring 1 out of 10 cases, If we uses it continuously. This issue is only generating in iPhone X or XR. In other device everything works fine.
If we talk about the code, I used the code which works perfectly. Here is a code snippet for UIRefreshControl.
Initialization:
lazy var refreshControl: UIRefreshControl? = {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action:#selector(refreshAssignmentData(_:)), for: .valueChanged)
refreshControl.tintColor = CustomColors.themeColorGreen
return refreshControl
}()
I call this func in API, so when I get response it will add.
func addRefreshControl() {
/// Here adding refresh control
if #available(iOS 10.0, *) {
self.tblView?.refreshControl = self.refreshControl
} else {
self.tblView?.addSubview(self.refreshControl!)
}
refreshControl?.alpha = 1
}
And when we use pull to refresh this method calls
#objc func refreshAssignmentData(_ sender: Any) {
DispatchQueue.global(qos: .background).async {
self.performSelector(inBackground: #selector(self.getNewAssignmentListInBackground(_:)), with: nil)
}
DispatchQueue.main.async {
self.arrAllAssignment?.removeAll()
self.arrAllAssignment = [AssignmentModel]()
self.intPage = 1
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.refreshControl?.endRefreshing()
}
}
All these lines of code working fine in other device range except iPhone X series.
Any suggestion please...

UIRefreshControl control spins forever after inserting new row

I have a UITableView, that on the delegate of the UIViewController that adds a new item, inserts the row. The row gets correctly inserted.
However, the UIRefreshControl on the UITableView will not go away when dragging the UITableView after that.
extension FeedViewController: AddPostDelegate {
func didAddPost(_ userPost: UserPost) {
self.postsWrapper?.posts.insert(PostWrapper(type: PostType.user(userPost)), at: 0)
self.tableView.beginUpdates()
self.tableView.insertRows(at: [
IndexPath(row: 1, section: 0)
], with: .automatic)
self.tableView.endUpdates()
self.refresher.endRefreshing()//Does nothing
}
}
In viewDidLoad:
refresher = UIRefreshControl()
tableView.addSubview(refresher)
refresher.addTarget(self, action: #selector(didDragScrollView), for: .valueChanged)
refresher.layer.zPosition = -1//otherwise in front of the header cell when refreshing (iOS 9.2)
If I do not go to another view first than come back before attempting to pull for refresh, it always hangs spinning forever.
EDIT:
It looks like the UIRefreshControl is no longer calling the target function AFTER I add a post.
Any ideas on why this may be occurring? How to fix that?
Make sure that beginRefreshing() was not called before the user pulled to refresh. If refreshControl.isRefreshing is true then the selector does not get called upon "pull to refresh".
A simple test:
let refreshControl = UIRefreshControl()
override func viewDidLoad() {
super.viewDidLoad()
refreshControl.addTarget(self, action: #selector(ViewController.refreshData), for: .valueChanged)
tableView.refreshControl = refreshControl
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// sets refreshControl.isRefreshing to true
// before the user initiates it
refreshControl.beginRefreshing()
}
Then the user pulls to refresh. Because refreshControl.isRefreshing is true the selector will not get called. If you remove refreshControl.beginRefreshing()in viewDidAppear, the selector will get called:
#objc func refreshData() {
print("refresh initiated")
refreshControl.endRefreshing()
}
Try assigning your UIRefreshControl into the refreshControl property of UITableView like this:
if #available(iOS 10.0, *) {
tableView.refreshControl = refresher
} else {
tableView.addSubview(refresher)
}
Note that this requires iOS 10.0 or higher, but we're soon getting iOS 12.0 so I don't think you should support iOS lower than 10.0

using pull_to_refresh in view controller in Swift

i'm so beginner developer in ios world, and it's the first time i try to make a little application.
my app used UItableview to show the content i've get it from server, i use this tutorial: https://www.youtube.com/watch?v=JG_AMY_gSDQ
in this tutorial: they used the main view controller and add table view to it, so when i try to add pull to refresh to my view, i can't because i didn't use UITableViewController.
so is there any way to refresh my content without using UITableViewController ?
and thank you so mush for answering.
Yes this is possible. You will need to initialize UIRefreshControl manually then addsubview to your tableView
var refreshControl:UIRefreshControl!
override func viewDidLoad() {
super.viewDidLoad()
refreshControl = UIRefreshControl()
refreshControl.tintColor = UIColor.redColor()
refreshControl.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
myTableView.addSubview(refreshControl)
}
func refresh(sender: AnyObject){
// this function will be called whenever you pull your list for refresh
}

Can I use a UIRefreshControl in a UIScrollView?

I have about 5 UIScrollView's already in my app which all load multiple .xib files. We now want to use a UIRefreshControl. They are built to be used with UITableViewControllers (per UIRefreshControl class reference). I do not want to re-do how all 5 UIScrollView work. I have already tried to use the UIRefreshControl in my UIScrollView's, and it works as expected except for a few things.
Just after the refresh image turns into the loader, the UIScrollView jumps down about 10 pixels, which only does not happen when I am very careful to drag the UIScrollview down very slowly.
When I scroll down and initiate the reload, then let go of the UIScrollView, the UIScrollView stays where I let it go. After it is finished reloading, the UIScrollView jumps up to the top with no animation.
Here is my code:
-(void)viewDidLoad
{
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(handleRefresh:) forControlEvents:UIControlEventValueChanged];
[myScrollView addSubview:refreshControl];
}
-(void)handleRefresh:(UIRefreshControl *)refresh {
// Reload my data
[refresh endRefreshing];
}
Is there any way I can save a bunch of time and use a UIRefreshControl in a UIScrollView?
Thank You!!!
I got a UIRefreshControl to work with a UIScrollView:
- (void)viewDidLoad
{
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
scrollView.userInteractionEnabled = TRUE;
scrollView.scrollEnabled = TRUE;
scrollView.backgroundColor = [UIColor whiteColor];
scrollView.contentSize = CGSizeMake(500, 1000);
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(testRefresh:) forControlEvents:UIControlEventValueChanged];
[scrollView addSubview:refreshControl];
[self.view addSubview:scrollView];
}
- (void)testRefresh:(UIRefreshControl *)refreshControl
{
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:#"Refreshing data..."];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:3];//for 3 seconds, prevent scrollview from bouncing back down (which would cover up the refresh view immediately and stop the user from even seeing the refresh text / animation)
dispatch_async(dispatch_get_main_queue(), ^{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMM d, h:mm a"];
NSString *lastUpdate = [NSString stringWithFormat:#"Last updated on %#", [formatter stringFromDate:[NSDate date]]];
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:lastUpdate];
[refreshControl endRefreshing];
NSLog(#"refresh end");
});
});
}
Need to do the data update on a separate thread or it will lock up the main thread (which the UI uses to update the UI). So while the main thread is busy updating the data, the UI is also locked up or frozen and you never see the smooth animations or spinner.
EDIT: ok, I'm doing the same thing as OP and i've now added some text to it (ie, "Pull to Refresh") and it does need to get back onto the main thread to update that text.
Updated answer.
Adding to above answers, in some situations you can't set the contentSize (using auto layout perhaps?) or the contentSize's height is less than or equal the height of the UIScrollView. In these cases, the UIRefreshControl won't work because the UIScrollView won't bounce.
To fix this set the property alwaysBounceVertical to TRUE.
Since iOS 10 UIScrollView already has a refreshControl property. This refreshControl will appear when you create a UIRefereshControl and assign it to this property.
There's no need to add UIRefereshControl as a subview anymore.
func configureRefreshControl () {
// Add the refresh control to your UIScrollView object.
myScrollingView.refreshControl = UIRefreshControl()
myScrollingView.refreshControl?.addTarget(self, action:
#selector(handleRefreshControl),
for: .valueChanged)
}
#objc func handleRefreshControl() {
// Update your content…
// Dismiss the refresh control.
DispatchQueue.main.async {
self.myScrollingView.refreshControl?.endRefreshing()
}
}
A UIRefreshControl object is a standard control that you attach to any UIScrollView object
Code and quote from https://developer.apple.com/documentation/uikit/uirefreshcontrol
If and when you are fortunate enough to be supporting iOS 10+, you can now simply set the refreshControl of the UIScrollView. This works the same way as the previously existing refreshControl on UITableView.
Here is how you do this in C# / Monotouch. I cant find any samples for C# anywhere, so here it is.. Thanks Log139!
public override void ViewDidLoad ()
{
//Create a scrollview object
UIScrollView MainScrollView = new UIScrollView(new RectangleF (0, 0, 500, 600));
//set the content size bigger so that it will bounce
MainScrollView.ContentSize = new SizeF(500,650);
// initialise and set the refresh class variable
refresh = new UIRefreshControl();
refresh.AddTarget(RefreshEventHandler,UIControlEvent.ValueChanged);
MainScrollView.AddSubview (refresh);
}
private void RefreshEventHandler (object obj, EventArgs args)
{
System.Threading.ThreadPool.QueueUserWorkItem ((callback) => {
InvokeOnMainThread (delegate() {
System.Threading.Thread.Sleep (3000);
refresh.EndRefreshing ();
});
});
}
For the Jumping issue, Tim Norman's answer solves it.
Here is the swift version if you are using swift2:
import UIKit
class NoJumpRefreshScrollView: UIScrollView {
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
}
*/
override var contentInset:UIEdgeInsets {
willSet {
if self.tracking {
let diff = newValue.top - self.contentInset.top;
var translation = self.panGestureRecognizer.translationInView(self)
translation.y -= diff * 3.0 / 2.0
self.panGestureRecognizer.setTranslation(translation, inView: self)
}
}
}
}
How to do it in Swift 3:
override func viewDidLoad() {
super.viewDidLoad()
let scroll = UIScrollView()
scroll.isScrollEnabled = true
view.addSubview(scroll)
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(pullToRefresh(_:)), for: .valueChanged)
scroll.addSubview(refreshControl)
}
func pullToRefresh(_ refreshControl: UIRefreshControl) {
// Update your conntent here
refreshControl.endRefreshing()
}
I made UIRefreshControl work properly inside UIScrollView. I inherited UIScrollView, blocked changing of contentInset and overrided contentOffset setter:
class ScrollViewForRefreshControl : UIScrollView {
override var contentOffset : CGPoint {
get {return super.contentOffset }
set {
if newValue.y < -_contentInset.top || _contentInset.top == 0 {
super.contentOffset = newValue
}
}
}
private var _contentInset = UIEdgeInsetsZero
override var contentInset : UIEdgeInsets {
get { return _contentInset}
set {
_contentInset = newValue
if newValue.top == 0 && contentOffset.y < 0 {
self.setContentOffset(CGPointZero, animated: true)
}
}
}
}
For the Jumping issue, override contentInset only solves it before iOS 9.
I just tried a way to avoid jump issue:
let scrollView = UIScrollView()
let refresh = UIRefreshControl()
// scrollView.refreshControl = UIRefreshControl() this will cause the jump issue
refresh.addTarget(self, action: #selector(handleRefreshControl), for: .valueChanged)
scrollView.alwaysBounceVertical = true
//just add refreshControl and send it to back will avoid jump issue
scrollView.addSubview(refresh)
scrollView.sendSubviewToBack(refresh)
works on iOS 9 10 11 and so on,and I hope they(Apple) just fix the issue.
Starting with iOS 10 a UIScrollView has a refreshControl property that you can set to a UIRefreshControl. As always you do not need to manage the frame of the control. Just configure the control, set the target-action for the valueChanged event and assign to the property.
Regardless of whether you are using a plain scroll view, table view or collection view the steps to create a refresh control are the same.
if #available(iOS 10.0, *) {
let refreshControl = UIRefreshControl()
refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl.addTarget(self,
action: #selector(refreshOptions(sender:)),
for: .valueChanged)
scrollView.refreshControl = refreshControl
}
#objc private func refreshOptions(sender: UIRefreshControl) {
// Perform actions to refresh the content
// ...
// and then dismiss the control
sender.endRefreshing()
}
You can simply create an instance of the refresh control and add it at the top of the scroll view. then, in the delegate methods you adjust its behavior to your requirements.

Resources