How to customize UIRefreshControl with different image and position?
You can't add different loader without accessing private APIs, but you can add background image:
UIImageView *rcImageView =
[[UIImageView alloc] initWithImage:
[UIImage imageNamed: @"refreshControl.png"]];
[self.refreshControl insertSubview:rcImageView atIndex:0];
assuming self is an instance of UITableViewController subclass.
Image size you need is 320x43px (@2x 640x86px), the middle area (approximately 35px) will be covered by the loader animation.
I show application logo there...
Replace UIRefreshControl with UIImageView
Please try this code:
let refreshImage = UIImageView()
refreshImage.image = UIImage(named: "RefreshIcon")
refreshControl.backgroundColor = UIColor.clear
refreshControl.tintColor = UIColor.clear
refreshControl.addSubview(refreshImage)
tableview.addSubview(refreshControl)
and then to center it to the top, you can use this:
refreshImage.frame = refreshControl.bounds.offsetBy(dx: self.view.frame.size.width / 2 - 20 // half the width of the refreshImage, dy: 10)
refreshImage.frame.size.width = 40 // Whatever width you want
refreshImage.frame.size.height = 40 // Whatever height you want
[Swift4]UIRefreshControl Indicator appear at different position
This happens because of combination of things.
You're using large headers for navigation controller. That makes
TableViewController
(actually any scrollable view) to put itsrefreshControl
on navigation bar . This behaviour was introduced in iOS 11 https://developer.apple.com/documentation/uikit/uinavigationbar/2908999-preferslargetitlesYou're using two scrollable views and one of them binds to navigation bar and another is not. During debug I've found out that the first scrollable view in hierarchy attached its
refreshControl
to navigation bar.
So, that's means we can fix it by putting some fake or dummy scrollable view somewhere on the bottom of hierarchy in order to it binds its refreshControl
to navigation bar first. Starting from iOS 11 any scrollable view may have refreshControl
and its "binded" even if you don't declare it explicitly.
In your case you might just add UIScrollView
before container in this way:
- View
-- Safe Area
-- UIScrollView (dummy)
-- ContainerView
---- TableView 1
---- TableView 2
...
UITableView - UIRefreshControl with Background Image
Try to change zPosition
of refresh control. You should receive something like:
_refreshControl = [[UIRefreshControl alloc] init];
[_refreshControl addTarget:self action:@selector(refresh:) forControlEvents:UIControlEventValueChanged];
[_ tableView setRefreshControl:_refreshControl];
_refreshControl.layer.zPosition += 1;
Custom UIRefreshControl with Lottie
You don't see the animation playing while you are scrolling because every time scrollViewDidScroll
is called you restart the animation by calling play
. And because that function is called almost constantly while you scroll the animation does not have a chance to play more than its first frame. So it looks paused.
When implementing a custom Refresh Control you have to implement 3 phases:
1. User scrolls but the refresh has not been triggered yet
In this phase you probably want to show a progress in your animation depending on how far the user has scrolled. To do this you calculate the progress in scrollViewDidScroll
and pass it to the LOTAnimationView
's animationProgress
property.
To calculate this progress you have to know how far the user has to scroll down until the refresh is triggered. In my experience this happens at a contentOffset.y
of approximately 150
.
2. Refresh is triggered
When the user has scrolled down enough a .valueChanged
ControlEvent is triggered. When this happens you start the looping animation by calling play()
on the LOTAnimationView
3. Refresh is done
When the refreshing is completed you call endRefreshing()
on your custom Refresh Control. When this happens you stop the animation by calling stop()
on the LOTAnimationView
Check out this small example of a LottieRefreshControl that I use in one of my projects:
import UIKit
import Lottie
class LottieRefreshControl: UIRefreshControl {
fileprivate let animationView = Lottie.AnimationView(name: "searchAnimation")
fileprivate var isAnimating = false
fileprivate let maxPullDistance: CGFloat = 150
override init() {
super.init(frame: .zero)
setupView()
setupLayout()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func updateProgress(with offsetY: CGFloat) {
guard !isAnimating else { return }
let progress = min(abs(offsetY / maxPullDistance), 1)
animationView.currentProgress = progress
}
override func beginRefreshing() {
super.beginRefreshing()
isAnimating = true
animationView.currentProgress = 0
animationView.play()
}
override func endRefreshing() {
super.endRefreshing()
animationView.stop()
isAnimating = false
}
}
private extension LottieRefreshControl {
func setupView() {
// hide default indicator view
tintColor = .clear
animationView.loopMode = .loop
addSubview(animationView)
addTarget(self, action: #selector(beginRefreshing), for: .valueChanged)
}
func setupLayout() {
animationView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
animationView.centerXAnchor.constraint(equalTo: centerXAnchor),
animationView.centerYAnchor.constraint(equalTo: centerYAnchor),
animationView.widthAnchor.constraint(equalToConstant: 50),
animationView.heightAnchor.constraint(equalToConstant: 32)
])
}
}
Now you only have to call updateProgress
on the Refresh Control from scrollViewDidScroll
:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
refreshControl.updateProgress(with: scrollView.contentOffset.y)
}
UIRefreshControl is in wrong position in UITableViewController
This is a known bug with iOS7; sometimes the refresh control is put incorrectly in the front of the view hierarchy instead of back. You can counter part of the problem by sending it to back after layout:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self.refreshControl.superview sendSubviewToBack:self.refreshControl];
}
Animation will still be imperfect, but at least it will still be below the table view. Please open a bug report with Apple for this issue.
Also, as stated in another answer, you should not add the refresh control to the view hierarchy yourself. The table view controller will do that for you. But that is not the issue here.
Swift version
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
refreshControl?.superview?.sendSubview(toBack: refreshControl!)
}
Custom UIRefreshControl animation
You need scrollViewDidScroll. But this works on the class that uses the TableView/TableViewController. Maybe you can call a public animation method for your FormsUIRefreshControl from here using the contentOffset. In Objective C:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSLog(@"Y Offset: %f", scrollView.contentOffset.y);
}
Can you make a custom view for a UIRefreshControl in Swift?
Yes, you can add your custom view to refresh control by calling addSubview
method.
// Outlet of table view
@IBOutlet weak var tbl : UITableView!
// Create global variable of NVActivityIndicatorView
var nvActivityIndicator : NVActivityIndicatorView?
override func viewDidLoad() {
super.viewDidLoad()
// Create refresh control and add as subview in your table view.
let refreshControl = UIRefreshControl()
refreshControl.backgroundColor = .red
refreshControl.addTarget(self, action: #selector(pullToRefresh), for: .valueChanged)
tbl.addSubview(refreshControl)
// Create NVActivityIndicator view and add as subview inside refresh control
nvActivityIndicator = NVActivityIndicatorView(frame: refreshControl.bounds, type: NVActivityIndicatorType.ballRotate, color: .blue, padding: 0)
nvActivityIndicator?.backgroundColor = refreshControl.backgroundColor
nvActivityIndicator?.translatesAutoresizingMaskIntoConstraints = false
refreshControl.addSubview(nvActivityIndicator!)
// Add constraint to auto resize nvActivityIndicator as per refresh control view.
let nvActivityIndicator_top = NSLayoutConstraint(item: nvActivityIndicator!, attribute: .top, relatedBy: .equal, toItem: refreshControl, attribute: .top, multiplier: 1, constant: 0)
let nvActivityIndicator_bottom = NSLayoutConstraint(item: nvActivityIndicator!, attribute: .bottom, relatedBy: .equal, toItem: refreshControl, attribute: .bottom, multiplier: 1, constant: 0)
let nvActivityIndicator_leading = NSLayoutConstraint(item: nvActivityIndicator!, attribute: .leading, relatedBy: .equal, toItem: refreshControl, attribute: .leading, multiplier: 1, constant: 0)
let nvActivityIndicator_trailing = NSLayoutConstraint(item: nvActivityIndicator!, attribute: .trailing, relatedBy: .equal, toItem: refreshControl, attribute: .trailing, multiplier: 1, constant: 0)
// Add constrains
refreshControl.addConstraint(nvActivityIndicator_top)
refreshControl.addConstraint(nvActivityIndicator_bottom)
refreshControl.addConstraint(nvActivityIndicator_leading)
refreshControl.addConstraint(nvActivityIndicator_trailing)
// Set custom view background colour as per refreshControl background colour
refreshControl.tintColor = refreshControl.backgroundColor
}
@objc func pullToRefresh() {
// Start animation here.
nvActivityIndicator?.startAnimating()
}
I hope this will help you.
Related Topics
Background User Location When App Is Terminated/Suspended
Share Image with Hashtag via Uiactivityviewcontroller (Twitter, Facebook, Instagram)
Domain=Nsurlerrordomain Code 1202
Swrevealviewcontroller - Manually Switch to Another View
Passing Data to and from an Embedded Uiwebview
Apple Watch, Watchkit Extension and Main Application
Swrevealviewcontroller Close Rear View When Tapping Front View
How to Access iOS Private APIs in Swift
Override Uiappearance Property for Mfmailcomposeviewcontroller
Xcode 8 Gm Seed Storyboard Layout Issue
Get Average Color of Uiimage in Swift
Uicollectionview - iOS 10 - Crash on iPhone 6 Plus Simulator But Works on Real Device
Better Way to Get the User's Name from Device
Uinavigationcontroller Interactivepopgesturerecognizer Working Abnormal in iOS7