How to Customize Uirefreshcontrol with Different Image and Position

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.

  1. You're using large headers for navigation controller. That makes TableViewController (actually any scrollable view) to put its refreshControl on navigation bar . This behaviour was introduced in iOS 11 https://developer.apple.com/documentation/uikit/uinavigationbar/2908999-preferslargetitles

  2. You'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



Leave a reply



Submit