Reputation: 2600
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 !
Upvotes: 76
Views: 20712
Reputation: 25
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()
}
Upvotes: 0
Reputation: 960
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)
}
}
Upvotes: 1
Reputation: 2147
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()
......
Upvotes: 1
Reputation: 574
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()
}
}
Upvotes: 0
Reputation: 5038
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?
Upvotes: 0
Reputation: 5634
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()
}
}
Upvotes: 1
Reputation: 366
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.
Upvotes: 1
Reputation: 2199
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()
Upvotes: 28
Reputation: 121
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()
}
}
Upvotes: 7
Reputation: 11
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
}
}
}
Upvotes: 1
Reputation: 31
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
})
}
Upvotes: 3
Reputation: 321
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.
Upvotes: 2
Reputation: 363
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()
}
Upvotes: 3
Reputation: 14338
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 :)
Upvotes: 0
Reputation: 865
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];
Upvotes: 0
Reputation: 81
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.
Upvotes: 4
Reputation: 70466
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.
Upvotes: 3
Reputation: 14409
@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
.
Upvotes: 8
Reputation: 6559
Try setting the tintColor of your UIRefreshControl in viewWillAppear.
Upvotes: 0
Reputation: 6795
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
Upvotes: 54
Reputation: 557
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];
}];
Upvotes: -2