UIRefreshControl beginRefreshing() shows activity indicator, but it is not animating - iphone

I use this code to beginRefreshing for the first time i visit my UIViewController
var refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(self.loadPlayer), for: .valueChanged)
self.playersTable.addSubview(refreshControl)
self.refreshControl.beginRefreshing()
self.loadPlayer()
function loadPlayer is called and the spinner is shown, but the problem is the spinner does not animating at all, and it looks bad. Any solution to trigger RefreshControl valueChanged?
I have tried another code like this but didn`t work
self.refreshControl.layoutIfNeeded()
self.refreshControl.beginRefreshing()
self.playersTable.setContentOffset(CGPoint(x: 0, y: -self.refreshControl.frame.size.height), animated: true)
i know i could use UIActivityIndicatorView and only show it for the first time, but could i use only UIRefreshControl for efficiency ?

Had the exact same problem - what worked for me is to put the self.refreshControl.beginRefreshing() inside viewWillAppear -
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.refreshController beginRefreshing]; }

Related

UIRefreshControl weird jump when scrolling down with preferLargeTitles enabled

My UIRefreshControl is not working properly when I scroll down my collectionview. The refreshControl pops out even with a slight scrolling and causes the title to jump down a bit.
I tried with:
extendedLayoutIncludesOpaqueBars = true
And that fixes something but still does a small jump (almost unnoticeable) when scrolling down to engage the refresh control.
If I do prefersLargeTitles = false, it works fine, no jumping.
I spent hours trying to fix it thanks to the poor Apple documentation on UIRefreshControl but finally found a solution. You have to add your refreshControl in the viewDidAppear method, I was adding it on the viewDidLoad method:
override func viewDidAappear(_ animated: Bool) {
super.viewDidAppear(animated)
collectionView.refreshControl = refreshControl
}

Swift 3 Popover Dim Background

I have read multiple places with suggestions on how to accomplish this. I went with adding a UI view in the background and setting it to disable and then after showing the popover, setting the view to enable.
As you can see it looks to work nicely:
But I do have two problems. The first one is once the popover is presented, you can tap anywhere on the background to dismiss the popover. Is there anywhere to block this from happening? I assumed my background UIView would block any inputs.
Also, after the popover is dismissed, the screen is still dim. I tried the following but neither of them load after dismissing the popover so the View never gets set back to disable:
override func viewDidAppear(_ animated: Bool) {
dimView.isHidden = true
}
override func viewWillAppear(_ animated: Bool) {
dimView.isHidden = true
}
EDIT:
This is the code that I use to present the popover:
let popover = storyboard?.instantiateViewController(withIdentifier: "PopoverVC")
popover?.modalPresentationStyle = .popover
popover?.popoverPresentationController?.delegate = self as? UIPopoverPresentationControllerDelegate
popover?.popoverPresentationController?.sourceView = self.view
popover?.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
popover?.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
dimView.isHidden = false
self.present(popover!, animated: false)
I realize that, dimView is not in PopoverVC, add it into PopoverVC and handle dismiss when tap on it.After the popover is dismissed viewDidAppear and viewWillAppear will not be called. So your screen is still blurry.If you add dimView into Popover, hope you can solve these issuses
I think you could solve your two problems with the UIPopoverPresentationControllerDelegate and a protocol/ delegate to tell the presenting viewcontroller when your are dismissing and hide your dimView.
The first issue can be implemented like this:
extension YourViewController: UIPopoverPresentationControllerDelegate {
func popoverPresentationControllerShouldDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) -> Bool {
return false
}
For the second issue you can pass a function through delegation. Hopefully this link will help with that.
https://matteomanferdini.com/how-ios-view-controllers-communicate-with-each-other/
Cheers

TapGestureRecognizer not firing in swift

Hi I'm having no cards to used to fixed this. Here we go.
I have this five imageview this is the declaration of first imageview but basically they have same declaration
#IBOutlet weak var first: UIImageView!{
didSet{
first.tag = 1
first.addGestureRecognizer(tapGesture())
}
}
and Then the tapGesture() method is this
private func tapGesture() -> UITapGestureRecognizer {
let tapGesture = UITapGestureRecognizer(target: self, action: Selector("iconTapped:"))
tapGesture.numberOfTapsRequired = 1
tapGesture.numberOfTouchesRequired = 1
return tapGesture
}
the action when the imageview is tapped the iconTapped() method is called, this is the code
func iconTapped(sender:UITapGestureRecognizer){
if let tag = sender.view?.tag{
processGesture(tag)
}
}
I have put all imageview userInteractionEnable to true but still not firing. Could anyone help me?
Some factor may or may not help, inside the viewdidload I update constraints and do some animation to imageview, but I'm not adding any subview programmatically
after trying so many things, I look on my view hierarchy and figure out that the parent view where my UIImageView rest userInteractionEnable is set to false(unchecked) in storyboard.
in this case the parentview is my superview, to fixed that I need to add this in viewDidLoad()
self.view.userInteractionEnabled = true
or
click the parentView in storyboard and in attributes inspector check the User Interaction Enable.
ps. sorry for silly mistake but I'll keep this question posted there might be other encountering same problem.

iOS 7 UIRefreshControl tintColor not working for beginRefreshing

I'm trying to set a tintColor on my UIRefreshControl (building on iOS 7).
I enabled refreshing for the tableViewController in storyboard, then in my ViewController viewDidLoad method i did the following:
[self.refreshControl setTintColor:[UIColor redColor]];
So now, when I pull to refresh, the color of the refresh control is red indeed:
I want my view to update automatically when it appears, so I did:
- (void)viewDidAppear:(BOOL)animated{
[self.refreshControl beginRefreshing];
}
It didn't show the spinning wheel, according to https://stackoverflow.com/a/16250679/1809736, I added
[self.tableView setContentOffset:CGPointMake(0, -self.refreshControl.frame.size.height) animated:NO];
to force show it.
It shows it, but now it is back to default color:
If I try manually to pull to refresh afterwards, it is red.
I tried building it on iOS6 and it works as it should, so is that an iOS7 bug?
P.S.: it is not a problem with the simulator, I tried building it on device, same bug.
P.P.S: I built an example project, can you tell me if you have the same bug or if there is a problem in my code? Here is the link: http://d.pr/f/pGrV
Thanks a lot !
Hey just stumbled into this exact issue.
Interestingly I fixed my code by setting the contentOffset first then calling beginRefreshing
if(self.tableView.contentOffset.y == 0){
self.tableView.contentOffset = CGPointMake(0, -self.refreshControl.frame.size.height);
[self.refreshControl beginRefreshing];
}
You may want to animate this process:
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void){
self.tableView.contentOffset = CGPointMake(0, -self.refreshControl.frame.size.height);
} completion:^(BOOL finished) {
[self.refreshControl beginRefreshing];
}];
Hope this helps you.
W
SWIFT SOLUTION !
Insert the following code in the viewDidLoad:
self.refreshControl.tintColor = UIColor.orangeColor()
self.tableView.contentOffset = CGPointMake(0, -self.refreshControl.frame.size.height)
self.refreshControl.beginRefreshing()
Swift 3.1
self.refreshControl.tintColor = UIColor.orange
self.tableView.contentOffset = CGPoint(x:0, y:-self.refreshControl.frame.size.height)
self.refreshControl.beginRefreshing()
#william-george's answer set me in the right direction, but was giving me weird autolayout animation issues.
So here's the version that worked for me:
- (void)programaticallyRefresh {
// Hack necessary to keep UIRefreshControl's tintColor
[self.scrollView setContentOffset:CGPointMake(0, -1.0f) animated:NO];
[self.scrollView setContentOffset:CGPointMake(0, -self.refreshControl.frame.size.height) animated:YES];
[self.refreshControl beginRefreshing];
[self refresh];
}
-refresh is the method tied to the UIRefreshControl.
Add an extension for UIResfreshControl.
extension UIRefreshControl {
func beginRefreshingManually() {
self.tintColor = UIColor.white
if let scrollView = superview as? UIScrollView {
scrollView.setContentOffset(CGPoint(x: 0, y:scrollView.contentOffset.y - frame.height), animated: false)
}
beginRefreshing()
}
}
None of these answers are working for me correctly on iOS8, with the closest being #jpsim's answer but that still left an unsightly black refresh control during its fade-in animation (it would cross-fade between black and while over the course of the animation).
The solution that worked for me was to put this immediately after creating the refresh control in my viewDidLoad:
self.refreshControl = [[UIRefreshControl alloc] init];
self.refreshControl.tintColor = [UIColor whiteColor];
...
self.refreshControlHeight = self.refreshControl.frame.size.height;
[self.tableView setContentOffset:CGPointMake(0, -1) animated:NO];
[self.tableView setContentOffset:CGPointMake(0, 0) animated:NO];
Then to show the UIRefreshControl programmatically:
[self.tableView setContentOffset:CGPointMake(0, self.tableView.contentOffset.y-self.refreshControlHeight) animated:YES];
[self.refreshControl beginRefreshing];
I had to store the height of the refresh control, as while it was set for the first invocation, subsequent calls would have a 0 height.
Solution for the tintColor issue: add this in viewDidLoad
[self.refreshControl setTintColor:[UIColor whiteColor]];
[self.refreshControl tintColorDidChange];
Now you have a white indicator when you call beginRefresh manually.
SWIFT:
I am using Swift and > iOS8. Most of the described workarounds didn't work for me. That's how I got it working:
In viewDidLoad:
customRefreshControl.tintColor = UIColor.clearColor()
The following doesn't have to be inside viewDidLoad. I put it in an extra function which get's called every time I update the tableView:
private func startRefreshControlAnimation() {
self.tableView.setContentOffset(CGPointMake(0, -self.customRefreshControl.frame.size.height), animated: true)
CATransaction.begin()
self.customRefreshControl.beginRefreshing()
CATransaction.commit()
}
I combined some of the previous answers. This works for me on iOS 9 and Swift 2:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let contentOffset = self.tableView.contentOffset.y
UIView.animateWithDuration(0, delay: 0, options: .BeginFromCurrentState, animations: {
print(self.tableView.contentOffset.y)
self.tableView.setContentOffset(CGPointMake(0, -self.refreshControl.frame.size.height), animated: false)
}, completion: { finished in
self.refreshControl.beginRefreshing()
self.tableView.setContentOffset(CGPointMake(0, contentOffset/2-self.refreshControl.frame.size.height), animated: true)
self.refresh() // Code that refresh table data
})
}
I develop for iOS using Xamarin (C#) and came across the same issue.
I fixed the coloring issue, by setting the AttributedTitle of the RefreshControl :
private CGPoint originalOffset;
...
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
...
originalOffset = TableView.ContentOffset; // Store the original offset of the table view
RefreshControl = new UIRefreshControl (){ TintColor = UIColor.Red };
RefreshControl.ValueChanged += ((s,e) => { Update (this, EventArgs.Empty); });
// Hack so the TintColor of the RefreshControl will be properly set
RefreshControl.AttributedTitle = new NSAttributedString ("Fetching data");
}
My Update method looks like this :
private async void Update(object sender, EventArgs args)
{
try {
TableView.UserInteractionEnabled = false;
// I find -100 to be a big enough offset
TableView.SetContentOffset (new CGPoint (0, -100), true);
RefreshControl.BeginRefreshing ();
... // Fetch data & update table source
TableView.ReloadData ();
} catch(Exception) {
// Respond to exception
} finally {
// Put the offset back to the original
TableView.SetContentOffset (originalOffset, true);
RefreshControl.EndRefreshing ();
TableView.UserInteractionEnabled = true;
}
}
Once the ViewDidAppear, I call Update programmatically.
Before setting the attributed title, my spinner would've been black.
Now it has the proper red color.
It's worth noticing, that this 'hack/fix' also comes with a second bug.
The first time you refresh, you'll notice that the AttributedTitle is not displayed.
Refreshing a second (,third,fourth,...) time will display the title properly. But if you don't want a title, you just initialize it with an empty string, and this is not a big issue to you.
I hope this can be of use to others.
this hack is very working
var refreshWasProgramBeginning: Bool = false
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !refreshWasProgramBeginning {
UIView.animate(withDuration: 0.25, animations: {
self.tableView.contentOffset = CGPoint.init(x: 0, y: -self.refreshControl.frame.height)
}) { (_) in
self.refreshControl.beginRefreshing()
self.refreshWasProgramBeginning = true
}
}
}
I am working with Xamarin C# (iOS 10) and found that a combination of all of these answers is what fixed it for me.
In my ViewDidLoad I have the following:
RefreshControl = new UIRefreshControl();
RefreshControl.TintColor = UIColor.White;
RefreshControl.ValueChanged += OnRefresh;
RefreshControl.BackgroundColor = UIColor.Clear;
And later I programmatically call the refresh animation in my ViewDidAppear with the following:
BeginInvokeOnMainThread(() =>
{
UIView.Animate(0, 0.2, UIViewAnimationOptions.BeginFromCurrentState, () =>
{
TableView.SetContentOffset(new CGPoint(0, TableView.ContentOffset.Y - RefreshControl.Frame.Size.Height), true);
RefreshControl.AttributedTitle = new NSAttributedString("");
},
() =>
{
RefreshControl.BeginRefreshing();
});
});
Note the setting of the attributed title and animation block were the parts I was missing for my RefreshControl to take my white tint color.
Thanks to all that have contributed to this question.
This is a bug which occurs when calling beginRefreshing() on the refresh control right after setting its tintColor property (or calling it from viewDidLoad() (details here). There is an easy workaround however, by wrapping the beginRefreshing() call inside a defer statement (Swift 4):
override func viewDidLoad() {
super.viewDidLoad()
refreshControl.tintColor = .red
defer {
refreshControl.beginRefreshing()
}
}
Set manually content offset for your tableView/scrollView before begin spinning:
tableView.setContentOffset(CGPoint(x: 0, y: tableView.contentOffset.y - (refreshControl.frame.size.height)), animated: true)
refreshControl.beginRefreshing()
......
Solution without adjusting content inset.
So you're going to hide original activity indicator replacing it with yours activity indicator (or custom view like LottieAnimationView).
import UIKit
final class CustomRefreshControl: UIRefreshControl {
private let loaderView = UIActivityIndicatorView(frame: .zero)
override init() {
super.init(frame: .zero)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
override func beginRefreshing() {
super.beginRefreshing()
self.loaderView.startAnimating()
}
override func endRefreshing() {
super.endRefreshing()
self.loaderView.stopAnimating()
}
override func didAddSubview(_ subview: UIView) {
super.didAddSubview(subview)
subview.alpha = subview === self.loaderView ? 1 : 0
}
}
// MARK: - Private
private extension CustomRefreshControl {
func setup() {
self.tintColor = .clear // hides default indicator view only when user pulls
self.addSubview(self.loaderView)
self.loaderView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.loaderView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
self.loaderView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
self.loaderView.heightAnchor.constraint(equalToConstant: 32),
self.loaderView.widthAnchor.constraint(equalToConstant: 32)
])
self.addTarget(self, action: #selector(self.beginRefreshing), for: .valueChanged)
}
}
Try setting the tintColor of your UIRefreshControl in viewWillAppear.
i found some Work Around i hope it works for you
[_TBL setContentOffset:CGPointMake(0,_TBL.contentOffset.y-_refreshControl.frame.size.height) animated:YES];
[_refreshControl performSelector:#selector(beginRefreshing) withObject:nil afterDelay:0.25];
[self getLatestUpdates];
I created a drop-in UIRefreshControl+beginRefreshing category that fixes this issue.
In brief, it fixes tintColor issue and manually tableView adjust contentOffset to make sure the refresh control is visible. Please try :)
When I set
tableView.refreshControl = refreshControl
several times where refreshControl is a different instance each time, I had the issue when refresh control color was always black and setting tint color to a different value didn't help.
So that I set tableView.refreshControl = refreshControl only once and when I need to hide it I set alpha value, more details in this thread:
How do I "hide" a UIRefreshControl?
Using UIView.animate didn't work for me on Swift 4.
Here's what I ended up using
extension UIRefreshControl {
func beginRefreshingManually(with scrollView: UIScrollView, isFirstTime: Bool) {
if self.isRefreshing { return }
// Workaround: If we call setContentOffset on the first time that the screen loads
// we get a black refreshControl with the wrong size.
// We could just set the scrollView.contentOffset everytime, but it does not animate the scrolling.
// So for every other time, we call the setContentOffset animated.
if isFirstTime {
scrollView.contentOffset = CGPoint(x: 0, y: -self.frame.size.height)
} else {
scrollView.setContentOffset(CGPoint(x: 0, y: -self.frame.size.height), animated: true)
}
self.beginRefreshing()
}
}
I've been struggling with this issue myself for a couple days now.
This is how I fixed it on my project:
I have a property on my VC for my refreshControl:
private lazy var refreshControl: UIRefreshControl = {
let refresh = UIRefreshControl()
refresh.tintColor = UIColor.yellow
refresh.addTarget(self, action: #selector(doSomething), for: .valueChanged)
return refresh
}()
On my viewDidAppear method, I was using
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tableView.setContentOffset(CGPoint(x: 0, y: -refreshControl.frame.height), animated: true)
refreshControl.beginRefreshing()
}
Well, turns out that if you set the animated property of setContentOffset to false, the refresh control tint color appears correctly from the very first time...
Corrected code, working for me:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tableView.setContentOffset(CGPoint(x: 0, y: -refreshControl.frame.height), animated: false)
refreshControl.beginRefreshing()
}
Force the setTintColor to run in the main thread. (Main thread updates the ui).
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
[self.refreshControl setTintColor:[UIColor redColor]];
[self.refreshControl beginRefreshing];
}];

Reset scroll on UICollectionView

I have a horizontal UICollectionView which works fine and scrolls. When I tap an item I update my data and call reloadData. This works and the new data is displayed in the UICollectionView.
The problem is the scroll position doesn't change and it is still viewing the last place. I want to reset the scrolling to the top (Or left in my case). How can I do this?
You want setContentOffset:. It takes a CGPoint as and argument that you can set to what ever you want using CGPointMake, but if you wish to return to the very beginning of the collection, you can simply use CGPointZero.
[collectionView setContentOffset:CGPointZero animated:YES];
You can use this method to scroll to any item you want:
- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath
atScrollPosition:(UICollectionViewScrollPosition)scrollPosition
animated:(BOOL)animated
I use this quite often in different parts of my app so I just extended UIScrollView so it can be used on any scroll view and scroll view subclass:
extension UIScrollView {
/// Sets content offset to the top.
func resetScrollPositionToTop() {
self.contentOffset = CGPoint(x: -contentInset.left, y: -contentInset.top)
}
}
So whenever I need to reset the position:
self.collectionView.resetScrollPositionToTop()
In Swift:
collectionView.setContentOffset(CGPointZero, animated: true)
If you leave the view that houses the collection view, make a change in the 2nd view controller, and need the collection view to update upon returning:
#IBAction func unwindTo_CollectionViewVC(segue: UIStoryboardSegue) {
//Automatic table reload upon unwind
viewDidLoad()
collectionView.reloadData()
collectionView.setContentOffset(CGPointZero, animated: true)
}
In my situation the unwind is great because the viewDidLoad call will update the CoreData context, the reloadData call will make sure the collectionView updates to reflect the new CoreData context, and then the contentOffset will make sure the table sets back to the top.
If you view controller contains safe area, code:
collectionView.setContentOffset(CGPointZero, animated: true)
doesn't work, so you can use universal extension:
extension UIScrollView {
func scrollToTop(_ animated: Bool) {
var topContentOffset: CGPoint
if #available(iOS 11.0, *) {
topContentOffset = CGPoint(x: -safeAreaInsets.left, y: -safeAreaInsets.top)
} else {
topContentOffset = CGPoint(x: -contentInset.left, y: -contentInset.top)
}
setContentOffset(topContentOffset, animated: animated)
}
}
CGPointZero in my case shifted the content of the collection view because you are not taking in count the content inset. This is what it worked for me:
CGPoint topOffest = CGPointMake(0,-self.collectionView.contentInset.top);
[self.collectionView setContentOffset:topOffest animated:YES];
Sorry I couldn't comment at the time of this writing, but I was using a UINavigationController and CGPointZero itself didn't get me to the very top so I had to use the following instead.
CGFloat compensateHeight = -(self.navigationController.navigationBar.bounds.size.height+[UIApplication sharedApplication].statusBarFrame.size.height);
[self.collectionView setContentOffset:CGPointMake(0, compensateHeight) animated:YES];
Hope this helps somebody in future. Cheers!
To deal with an UINavigationController and a transparent navigation bar, I had to calculate the extra top offset to perfectly match the position of the first element.
Swift 1.1 code:
let topOffest = CGPointMake(0, -(self.collectionView?.contentInset.top ?? O))
self.collectionView?.setContentOffset(topOffest, animated: true)
Cheers!
SWIFT 4.2 SOLUTION-
Say I have a tableView with each tableViewCell containing a HorizontalCollectionView
Due to reuse of cells, even though first tableViewCell is scrolled to say page 4, downwards, other cell is also set to page 4.
This works in such cases-
In the cellForRowAt func in tableView,
let cell = //declaring a cell using deque
cell.myCollectionView.contentOffset = CGPoint(x:0,y:0)