refreshControl extension does not show spinner after imagePicker - ios

I wrote an extension that allows me to show a refreshControl on a regular tableView in a UIViewController. The code below works perfectly on the viewDidLoad() and when pull to refresh.
Extension:
extension UITableView
{
func findRefreshControl () -> UIRefreshControl?
{
for view in subviews
{
if view is UIRefreshControl
{
print("The refresh control: \(view)")
return view as? UIRefreshControl
}
}
return nil
}
func addRefreshControlWith(sender: UIViewController, action: Selector, view: UIView)
{
guard findRefreshControl() == nil else { return }
let refreshControl = UIRefreshControl()
refreshControl.addTarget(sender, action: action, for: UIControlEvents.valueChanged)
view.addSubview(refreshControl)
}
func showRefreshControlWith(attributedTitle: String? = nil)
{
findRefreshControl()?.attributedTitle = NSAttributedString(string: attributedTitle!)
findRefreshControl()?.beginRefreshing()
}
func hideRefreshControl ()
{
findRefreshControl()?.endRefreshing()
}
}
Callees in ViewController: (Triggerend by observe events)
private func showLoader()
{
tableView.showRefreshControlWith(attributedTitle: "Loading Profile".localized())
}
private func hideLoader()
{
tableView.hideRefreshControl()
}
Observers:
_ = presenter.profilePictureUploadingPending.skip(first: 1).observeNext { _ in self.showLoader() }
_ = presenter.profilePictureUploaded.skip(first: 1).observeNext { _ in self.hideLoader() }
However, when I enter the UIImagePicker to upload a photo in my app and return back to the viewController it should trigger the the spinning process again until the photo is uploaded successfully.
I debugged my code and it is firing up the methods but the spinner disappeared from the view hierarchy subviews and is not showing up...
I am not sure how i can solve this issue but I really appreciate any help from you guys!
Thanks,
Kevin.

Solved with following solution:
private func showLoader()
{
DispatchQueue.global(qos: .default).async {
DispatchQueue.main.async {
self.tableView.contentOffset.x = 1
self.tableView.contentOffset.y = -81
self.tableView.showRefreshControlWith(attributedTitle: "LOADING_PROFILE".localized())
}
}
}

Related

UIScrollView bar insets the size of the tab bar and nav bar

I am using the library Chatto (https://github.com/badoo/Chatto) for a chat window and initializing a view controller that inherits from it's BaseChatViewController.
The issue that i'm having is that while the scroll view can scroll all the way to the top and bottom of the conversation, the place where the scroll bar stops is inset about the same size as the nav bar on the top and the tab bar on the bottom.
The Chatto demo app doesn't appear to have this issue, so I'm wondering if there is something I should be doing differently in my app.
Here is the viewController
import Chatto
import ChattoAdditions
import RxSwift
import UIKit
class ConversationViewController: BaseChatViewController {
var viewModel: ConversationViewModel!
private let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
title = viewModel.title
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadViewModel()
}
override func createChatInputView() -> UIView {
return viewModel.chatInputView
}
override func createPresenterBuilders() -> [ChatItemType: [ChatItemPresenterBuilderProtocol]] {
return viewModel.presenterBuilders
}
}
// MARK: - MessagesSelectorDelegate
extension ConversationViewController: MessagesSelectorDelegate {
func messagesSelector(_: MessagesSelectorProtocol, didSelectMessage _: MessageModelProtocol) {
enqueueModelUpdate(updateType: .normal)
}
func messagesSelector(_: MessagesSelectorProtocol, didDeselectMessage _: MessageModelProtocol) {
enqueueModelUpdate(updateType: .normal)
}
}
// MARK: - Helpers
extension ConversationViewController {
private func loadViewModel() {
ActivityIndicator.shared.show(on: view)
viewModel.load()
.subscribe { [weak self] event in
guard let self = self else {
Log.error(
"The ConversationViewController, while reacting to it's viewModel loading, was unable to unwrap self"
)
return
}
ActivityIndicator.shared.hide()
switch event {
case .completed:
return
case let .error(error):
Log.error(error)
self.presentErrorAlert(error)
}
}
.disposed(by: disposeBag)
}
}

Pull to refresh and activity indicator on a view

I've a VC containing Table View. I've implemented pull to refresh on table view and activity indicator on view. On pull to refresh table view flickers as it looks like it has finished refreshing and reloads while I pull down and keep holding the table view. I'm not sure where it's going wrong. Following is my code. How to I implement it correctly?
class MyViewController: CustomVC {
override func viewDidLoad() {
super.viewDidLoad()
self.addRefreshControl(to: tableView)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.view.activityStartAnimating()
self.tableView.isHidden = true
loadContent(isRefresh: false)
}
private func loadContent(isRefresh: Bool) {
fetchContent() { (result, error) in
// ....
if isRefresh {
self.refreshControl.endRefreshing()
} else {
self.view.activityStopAnimating()
}
// ...
self.tableView.reloadData()
self.tableView.isHidden = false
}
}
// Pull to refresh
override func fetchDataForRefresh() {
loadContent(isRefresh: true)
}
}
Custom VC class
class CustomVC: UIViewController {
lazy var refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(refresh(_:)), for: UIControl.Event.valueChanged)
refreshControl.transform = CGAffineTransform(scaleX: 0.87, y: 0.87)
return refreshControl
}()
override func viewDidLoad() {
super.viewDidLoad()
}
func addRefreshControl(to tableView: UITableView) {
if #available(iOS 10.0, *) {
tableView.refreshControl = refreshControl
} else {
tableView.addSubview(refreshControl)
}
tableView.indicatorStyle = .white
}
#objc func refresh(_ refreshControl: UIRefreshControl) {
fetchDataForRefresh()
}
func fetchDataForRefresh() {
}
}
Fetch Content
func fetchContent() {
// .....
DispatchQueue.main.async {
completed(resultData,nil)
return
}
//....
}
Try this technique
func setupRefreshControl() {
refreshControl = UIRefreshControl()
refreshControl?.addTarget(self, action: #selector(refreshData), for: .valueChanged)
tableView?.refreshControl = refreshControl
}
When you finished fetching your data:
DispatchQueue.main.async {
self.refreshControl?.perform(#selector(UIRefreshControl.endRefreshing), with: nil, afterDelay: 0)
self.tableView.reloadData()
}
Please note I have access to refreshControl because I'm inside a UITableViewController but you can easily add it in a UIViewController as well.
Remove the if refresh else ... from your code and put your reload your UITableView and endRefreshing on the main thread as I mentioned above

Creating a clickable UIImageView using an extension

I have the following Swift code.
extension UIImageView {
func enableClickablePrint() {
let imageTap = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
self.addGestureRecognizer(imageTap)
self.isUserInteractionEnabled = true
}
func disableClickablePrint() {
// HERE
}
func toggleClickablePrint() {
// HERE
}
#objc fileprivate func imageTapped(_ sender: UITapGestureRecognizer) {
print("Image tapped")
}
}
The problem I'm running into is how to fill out the disableClickablePrint and toggleClickablePrint functions.
I'd like to be able to do something like the following.
extension UIImageView {
var imageTap: UITapGestureRecognizer?
func enableClickablePrint() {
imageTap = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
self.addGestureRecognizer(imageTap)
self.isUserInteractionEnabled = true
}
func disableClickablePrint() {
if let theImageTap = imageTap {
self.removeGestureRecognizer(theImageTap)
imageTap = nil
}
}
func toggleClickablePrint() {
if let theImageTap = imageTap {
disableClickablePrint()
} else {
enableClickablePrint()
}
}
#objc fileprivate func imageTapped(_ sender: UITapGestureRecognizer) {
print("Image tapped")
}
}
But of course the problem is you can't store properties in extensions like I'm wanting to do.
Anyway to achieve this? I want to try to keep this as clean as possible, without resorting to fancy tricks unless absolutely required.
Would the correct thing to do to be to convert this into a subclass of UIImageView? I'd like to try to avoid that if possible just because if I want to turn this into a framework or library or something, subclasses don't integrate as nicely into interface builder and the developer would have to add the extra step of changing the class of all their image views. Which I think would be awesome to avoid if possible.
Your problem is that you need to be able to recognize the UIGestureRecognizer you added, but you can't store it in a property.
Here's a (tested) solution that subclasses UITapGestureRecognizer to make the UIGestureRecognizer identifiable and then searches self.gestureRecognizers with first(where:) to see if one has been added:
extension UIImageView {
class _CFTapGestureRecognizer : UITapGestureRecognizer { }
private var _imageTap: _CFTapGestureRecognizer? { return self.gestureRecognizers?.first(where: { $0 is _CFTapGestureRecognizer }) as? _CFTapGestureRecognizer }
func enableClickablePrint() {
// Only enable once
if _imageTap == nil {
let imageTap = _CFTapGestureRecognizer(target: self, action: #selector(imageTapped))
self.addGestureRecognizer(imageTap)
self.isUserInteractionEnabled = true
}
}
func disableClickablePrint() {
if let theImageTap = _imageTap {
self.removeGestureRecognizer(theImageTap)
}
}
func toggleClickablePrint() {
if _imageTap == nil {
enableClickablePrint()
} else {
disableClickablePrint()
}
}
#objc fileprivate func imageTapped(_ sender: UITapGestureRecognizer) {
print("Image tapped")
}
}
I've use this extension for years and it work not only image but with any view.
extension UIView {
func addTapGuesture(target: Any, action: Selector) {
let tap = UITapGestureRecognizer(target: target, action: action)
tap.numberOfTapsRequired = 1
addGestureRecognizer(tap)
isUserInteractionEnabled = true
}
}
Usage:
imageView.addTapGuesture(target: self, action: #selector(imageTapped))
#objc func imageTapped() {
print("Tapped")
}
You could make an extension variable using a getter/setter:
extension UIImageView {
var tap: UITapGestureRecognizer {
get {
return //the created tap
}
set(value) {
print(value)
}
}
}
Since Extensions in swift can't contain stored properties, these are the solutions:
Fisrt one is the most used workaround is to use Objective_C runtime, by using objc_getAssociatedObject and objc_setAssociatedObject functions.
OK it's a nice solution, but if there is pure swift approach to do that so why not!
In your extension, define a struct with the field that you want to use, here you want for example UITapGestureRecognizer
Tip Create this struct as private You don't need anyone to access it of course.
Then define a computed property that will use this struct....
By doing this you have achieved what you need and you don't use the Objective-C at all .
Example :
extension UIImageView {
private struct TapGestureHelper{
static var tapGestureRecognizer : UITapGestureRecognizer?
}
var imageTap: UITapGestureRecognizer?{
get {
return TapGestureHelper.tapGestureRecognizer
}
set {
TapGestureHelper.tapGestureRecognizer = newValue
}
}
func enableClickablePrint() {
imageTap = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
self.addGestureRecognizer(imageTap!)
self.isUserInteractionEnabled = true
}
func disableClickablePrint() {
guard let gesture = self.gestureRecognizers?.first else {
return
}
self.removeGestureRecognizer(gesture)
self.imageTap = nil
}
func toggleClickablePrint() {
if let theImageTap = imageTap {
disableClickablePrint()
} else {
enableClickablePrint()
}
}
#objc fileprivate func imageTapped(_ sender: UITapGestureRecognizer) {
print("Image tapped")
}
}

Portrait/Landscape display ios swift 3

I have an application with 2 views.
On the first view, the user enter a login, hits on search button.
The application retrieves through an api information about the login,
is redirected on the second view where it displays information about the login.There is a back button on the second view to go back on the first view to be able to search a different login
In the second view, I treat both cases (Landscape/Portrait)
What happens is the following : first time I enter the login, Portrait and landscape cases are well treated on the result view.
When I go back and search a new login, It will only display the mode in which you entered the second time (if you entered in portrait mode, it will
only display in portrait mode, the same with landscape) Is there something that I should be doing ?
here is the code for the first view :
var login = ""
class FirstViewController: UIViewController {
#IBOutlet weak var Input: UITextField!
#IBAction func sendlogin(_ sender: UIButton) {
if let text = Input.text, !text.isEmpty {
login = text
performSegue(withIdentifier: "callaffiche", sender: self)
return
} else {
return
}
}
here is the code for the second view (I will only post viewDidLoad, the back button creation along its function and the creation of the imageuser as there are many objects. But all of them are created with the same principle ) AfficheElemts calls the creation of every single object( ButtonBackAffiche, ImageAffiche are called within AfficheElements
override func viewDidLoad() {
super.viewDidLoad()
print("ds viewdidload")
let qos = DispatchQoS.userInitiated.qosClass
let queue = DispatchQueue.global(qos: qos)
queue.sync {
self.aut.GetToken(completionHandler: { (hasSucceed) in
if hasSucceed {
print("good")
DispatchQueue.main.async {
self.AfficheElements()
}
} else {
print("bad")
}
})
}
}
func ButtonBackAffiche () {
let button = UIButton()
button.restorationIdentifier = "ReturnToSearch"
button.setTitle("Back", for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
button.isEnabled = true
button.addTarget(self, action: #selector(ReturnToSearch(button:)), for: .touchUpInside)
if UIDevice.current.orientation.isPortrait {
button.frame = CGRect (x:CGFloat(180),y:CGFloat(600),width:50,height:30)
} else {
button.frame = CGRect (x:CGFloat(350),y:CGFloat(360),width:50,height:30)
}
self.view.addSubview(button)
}
func ImageAffiche () {
//Get image (Data) from URL Internet
let strurl = NSURL(string: image_url)!
let dtinternet = NSData(contentsOf:strurl as URL)!
let bongtuyet:UIImageView = UIImageView ()
if UIDevice.current.orientation.isPortrait {
print("portrait ds imageview")
bongtuyet.frame = CGRect (x:10,y:80,width:30,height:30)
} else {
print("landscape ds imageview")
bongtuyet.frame = CGRect (x:CGFloat(200),y:CGFloat(80),width:90,height:120)
}
bongtuyet.image = UIImage(data:dtinternet as Data)
self.view.addSubview(bongtuyet)
}
func ReturnToSearch(button: UIButton) {
performSegue(withIdentifier: "ReturnToSearch", sender: self)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
print("ds viewwilltransition")
if UIDevice.current.orientation.isPortrait {
print("portrait")
} else {
print("landscape")
}
while let subview = self.view.subviews.last {
subview.removeFromSuperview()
}
AfficheElements()
}
One last thing, I have put traces in the code and the creation of the objects, it goes through the right portion of the code but does not display the objects at the right locations.
Thanks #Spads for taking time to help me.
I finally figured it out. I was not calling the function AfficheElements
int the main queue.
DispatchQueue.main.async {
self.AfficheElements()
}
in viewWillTransition solved it.

How to use pull to refresh in Swift?

I am building an RSS reader using swift and need to implement pull to reload functionality.
Here is how i am trying to do it.
class FirstViewController: UIViewController,
UITableViewDelegate, UITableViewDataSource {
#IBOutlet var refresh: UIScreenEdgePanGestureRecognizer
#IBOutlet var newsCollect: UITableView
var activityIndicator:UIActivityIndicatorView? = nil
override func viewDidLoad() {
super.viewDidLoad()
self.newsCollect.scrollEnabled = true
// Do any additional setup after loading the view, typically from a nib.
if nCollect.news.count <= 2{
self.collectNews()
}
else{
self.removeActivityIndicator()
}
view.addGestureRecognizer(refresh)
}
#IBAction func reload(sender: UIScreenEdgePanGestureRecognizer) {
nCollect.news = News[]()
return newsCollect.reloadData()
}
I am getting :
Property 'self.refresh' not initialized at super.init call
Please help me to understand the behaviour of Gesture recognisers. A working sample code will be a great help.
Thanks.
Pull to refresh is built in iOS. You could do this in swift like
let refreshControl = UIRefreshControl()
override func viewDidLoad() {
super.viewDidLoad()
refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl.addTarget(self, action: #selector(self.refresh(_:)), for: .valueChanged)
tableView.addSubview(refreshControl) // not required when using UITableViewController
}
#objc func refresh(_ sender: AnyObject) {
// Code to refresh table view
}
At some point you could end refreshing.
refreshControl.endRefreshing()
A solution with storyboard and Swift:
Open your .storyboard file, select a TableViewController in your storyboard and "Enable" the Table View Controller: Refreshing feature in the Utilities.
Open the associated UITableViewController class and add the following Swift 5 line into the viewDidLoad method.
self.refreshControl?.addTarget(self, action: #selector(refresh), for: UIControl.Event.valueChanged)
Add the following method above the viewDidLoad method
func refresh(sender:AnyObject)
{
// Updating your data here...
self.tableView.reloadData()
self.refreshControl?.endRefreshing()
}
UIRefreshControl is directly supported in each of UICollectionView, UITableView and UIScrollView (requires iOS 10+)!
Each one of these views has a refreshControl instance property, which means that there is no longer a need to add it as a subview in your scroll view, all you have to do is:
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(doSomething), for: .valueChanged)
// this is the replacement of implementing: "collectionView.addSubview(refreshControl)"
collectionView.refreshControl = refreshControl
}
#objc func doSomething(refreshControl: UIRefreshControl) {
print("Hello World!")
// somewhere in your code you might need to call:
refreshControl.endRefreshing()
}
Personally, I find it more natural to treat it as a property for scroll view more than adding it as a subview, especially because the only appropriate view to be as a superview for a UIRefreshControl is a scrollview, i.e the functionality of using UIRefreshControl is only useful when working with a scroll view; That's why this approach should be more obvious to set up the refresh control view.
However, you still have the option of using the addSubview based on the iOS version:
if #available(iOS 10.0, *) {
collectionView.refreshControl = refreshControl
} else {
collectionView.addSubview(refreshControl)
}
Swift 4
var refreshControl: UIRefreshControl!
override func viewDidLoad() {
super.viewDidLoad()
refreshControl = UIRefreshControl()
refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl.addTarget(self, action: #selector(refresh), for: .valueChanged)
tableView.addSubview(refreshControl)
}
#objc func refresh(_ sender: Any) {
// your code to reload tableView
}
And you could stop refreshing with:
refreshControl.endRefreshing()
Swift 5
private var pullControl = UIRefreshControl()
pullControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
pullControl.addTarget(self, action: #selector(refreshListData(_:)), for: .valueChanged)
if #available(iOS 10.0, *) {
tableView.refreshControl = pullControl
} else {
tableView.addSubview(pullControl)
}
// Actions
#objc private func refreshListData(_ sender: Any) {
self.pullControl.endRefreshing() // You can stop after API Call
// Call API
}
In Swift use this,
If you wants to have pull to refresh in WebView,
So try this code:
override func viewDidLoad() {
super.viewDidLoad()
addPullToRefreshToWebView()
}
func addPullToRefreshToWebView(){
var refreshController:UIRefreshControl = UIRefreshControl()
refreshController.bounds = CGRectMake(0, 50, refreshController.bounds.size.width, refreshController.bounds.size.height) // Change position of refresh view
refreshController.addTarget(self, action: Selector("refreshWebView:"), forControlEvents: UIControlEvents.ValueChanged)
refreshController.attributedTitle = NSAttributedString(string: "Pull down to refresh...")
YourWebView.scrollView.addSubview(refreshController)
}
func refreshWebView(refresh:UIRefreshControl){
YourWebView.reload()
refresh.endRefreshing()
}
Anhil's answer helped me a lot.
However, after experimenting further I noticed that the solution suggested sometimes causes a not-so-pretty UI glitch.
Instead, going for this approach* did the trick for me.
*Swift 2.1
//Create an instance of a UITableViewController. This will host your UITableView.
private let tableViewController = UITableViewController()
//Add tableViewController as a childViewController and set its tableView property to your UITableView.
self.addChildViewController(self.tableViewController)
self.tableViewController.tableView = self.tableView
self.refreshControl.addTarget(self, action: "refreshData:", forControlEvents: .ValueChanged)
self.tableViewController.refreshControl = self.refreshControl
Details
Xcode Version 10.3 (10G8), Swift 5
Features
Ability to make "pull to refresh" programmatically
Protection from multi- "pull to refresh" events
Ability to continue animating of the activity indicator when view controller switched (e.g. in case of TabController)
Solution
import UIKit
class RefreshControl: UIRefreshControl {
private weak var actionTarget: AnyObject?
private var actionSelector: Selector?
override init() { super.init() }
convenience init(actionTarget: AnyObject?, actionSelector: Selector) {
self.init()
self.actionTarget = actionTarget
self.actionSelector = actionSelector
addTarget()
}
private func addTarget() {
guard let actionTarget = actionTarget, let actionSelector = actionSelector else { return }
addTarget(actionTarget, action: actionSelector, for: .valueChanged)
}
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
func endRefreshing(deadline: DispatchTime? = nil) {
guard let deadline = deadline else { endRefreshing(); return }
DispatchQueue.global(qos: .default).asyncAfter(deadline: deadline) { [weak self] in
DispatchQueue.main.async { self?.endRefreshing() }
}
}
func refreshActivityIndicatorView() {
guard let selector = actionSelector else { return }
let _isRefreshing = isRefreshing
removeTarget(actionTarget, action: selector, for: .valueChanged)
endRefreshing()
if _isRefreshing { beginRefreshing() }
addTarget()
}
func generateRefreshEvent() {
beginRefreshing()
sendActions(for: .valueChanged)
}
}
public extension UIScrollView {
private var _refreshControl: RefreshControl? { return refreshControl as? RefreshControl }
func addRefreshControll(actionTarget: AnyObject?, action: Selector, replaceIfExist: Bool = false) {
if !replaceIfExist && refreshControl != nil { return }
refreshControl = RefreshControl(actionTarget: actionTarget, actionSelector: action)
}
func scrollToTopAndShowRunningRefreshControl(changeContentOffsetWithAnimation: Bool = false) {
_refreshControl?.refreshActivityIndicatorView()
guard let refreshControl = refreshControl,
contentOffset.y != -refreshControl.frame.height else { return }
setContentOffset(CGPoint(x: 0, y: -refreshControl.frame.height), animated: changeContentOffsetWithAnimation)
}
private var canStartRefreshing: Bool {
guard let refreshControl = refreshControl, !refreshControl.isRefreshing else { return false }
return true
}
func startRefreshing() {
guard canStartRefreshing else { return }
_refreshControl?.generateRefreshEvent()
}
func pullAndRefresh() {
guard canStartRefreshing else { return }
scrollToTopAndShowRunningRefreshControl(changeContentOffsetWithAnimation: true)
_refreshControl?.generateRefreshEvent()
}
func endRefreshing(deadline: DispatchTime? = nil) { _refreshControl?.endRefreshing(deadline: deadline) }
}
Usage
// Add refresh control to UICollectionView / UITableView / UIScrollView
private func setupTableView() {
let tableView = UITableView()
// ...
tableView.addRefreshControll(actionTarget: self, action: #selector(refreshData))
}
#objc func refreshData(_ refreshControl: UIRefreshControl) {
tableView?.endRefreshing(deadline: .now() + .seconds(3))
}
// Stop refreshing in UICollectionView / UITableView / UIScrollView
tableView.endRefreshing()
// Simulate pull to refresh in UICollectionView / UITableView / UIScrollView
tableView.pullAndRefresh()
Full Sample
Do not forget to add the solution code here
import UIKit
class ViewController: UIViewController {
private weak var tableView: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
}
private func setupTableView() {
let tableView = UITableView()
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.dataSource = self
tableView.delegate = self
tableView.addRefreshControll(actionTarget: self, action: #selector(refreshData))
self.tableView = tableView
}
}
extension ViewController {
#objc func refreshData(_ refreshControl: UIRefreshControl) {
print("refreshing")
tableView?.endRefreshing(deadline: .now() + .seconds(3))
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int { return 1 }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "\(indexPath)"
return cell
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.pullAndRefresh()
}
}
What the error is telling you, is that refresh isn't initialized. Note that you chose to make refresh not optional, which in Swift means that it has to have a value before you call super.init (or it's implicitly called, which seems to be your case). Either make refresh optional (probably what you want) or initialize it in some way.
I would suggest reading the Swift introductory documentation again, which covers this in great length.
One last thing, not part of the answer, as pointed out by #Anil, there is a built in pull to refresh control in iOS called UIRefresControl, which might be something worth looking into.
I built a RSS feed app in which I have a Pull To refresh feature that originally had some of the problems listed above.
But to add to the users answers above, I was looking everywhere for my use case and could not find it. I was downloading data from the web (RSSFeed) and I wanted to pull down on my tableView of stories to refresh.
What is mentioned above cover the right areas but with some of the problems people are having, here is what I did and it works a treat:
I took #Blankarsch 's approach and went to my main.storyboard and select the table view to use refresh, then what wasn't mentioned is creating IBOutlet and IBAction to use the refresh efficiently
//Created from main.storyboard cntrl+drag refresh from left scene to assistant editor
#IBOutlet weak var refreshButton: UIRefreshControl
override func viewDidLoad() {
......
......
//Include your code
......
......
//Is the function called below, make sure to put this in your viewDidLoad
//method or not data will be visible when running the app
getFeedData()
}
//Function the gets my data/parse my data from the web (if you havnt already put this in a similar function)
//remembering it returns nothing, hence return type is "-> Void"
func getFeedData() -> Void{
.....
.....
}
//From main.storyboard cntrl+drag to assistant editor and this time create an action instead of outlet and
//make sure arguments are set to none and note sender
#IBAction func refresh() {
//getting our data by calling the function which gets our data/parse our data
getFeedData()
//note: refreshControl doesnt need to be declared it is already initailized. Got to love xcode
refreshControl?.endRefreshing()
}
Hope this helps anyone in same situation as me
func pullToRefresh(){
let refresh = UIRefreshControl()
refresh.addTarget(self, action: #selector(handleTopRefresh(_:)), for: .valueChanged )
refresh.tintColor = UIColor.appBlack
self.tblAddressBook.addSubview(refresh)
}
#objc func handleTopRefresh(_ sender:UIRefreshControl){
self.callAddressBookListApi(isLoaderRequired: false)
sender.endRefreshing()
}
I suggest to make an Extension of pull To Refresh to use in every class.
1) Make an empty swift file : File - New - File - Swift File.
2) Add the Following
// AppExtensions.swift
import Foundation
import UIKit
var tableRefreshControl:UIRefreshControl = UIRefreshControl()
//MARK:- VIEWCONTROLLER EXTENSION METHODS
public extension UIViewController
{
func makePullToRefreshToTableView(tableName: UITableView,triggerToMethodName: String){
tableRefreshControl.attributedTitle = NSAttributedString(string: "TEST: Pull to refresh")
tableRefreshControl.backgroundColor = UIColor.whiteColor()
tableRefreshControl.addTarget(self, action: Selector(triggerToMethodName), forControlEvents: UIControlEvents.ValueChanged)
tableName.addSubview(tableRefreshControl)
}
func makePullToRefreshEndRefreshing (tableName: String)
{
tableRefreshControl.endRefreshing()
//additional codes
}
}
3) In Your View Controller call these methods as :
override func viewWillAppear(animated: Bool) {
self.makePullToRefreshToTableView(bidderListTable, triggerToMethodName: "pullToRefreshBidderTable")
}
4) At some point you wanted to end refreshing:
func pullToRefreshBidderTable() {
self.makePullToRefreshEndRefreshing("bidderListTable")
//Code What to do here.
}
OR
self.makePullToRefreshEndRefreshing("bidderListTable")
For the pull to refresh i am using
DGElasticPullToRefresh
https://github.com/gontovnik/DGElasticPullToRefresh
Installation
pod 'DGElasticPullToRefresh'
import DGElasticPullToRefresh
and put this function into your swift file and call this funtion from your
override func viewWillAppear(_ animated: Bool)
func Refresher() {
let loadingView = DGElasticPullToRefreshLoadingViewCircle()
loadingView.tintColor = UIColor(red: 255.0/255.0, green: 255.0/255.0, blue: 255.0/255.0, alpha: 1.0)
self.table.dg_addPullToRefreshWithActionHandler({ [weak self] () -> Void in
//Completion block you can perfrom your code here.
print("Stack Overflow")
self?.table.dg_stopLoading()
}, loadingView: loadingView)
self.table.dg_setPullToRefreshFillColor(UIColor(red: 255.0/255.0, green: 57.0/255.0, blue: 66.0/255.0, alpha: 1))
self.table.dg_setPullToRefreshBackgroundColor(self.table.backgroundColor!)
}
And dont forget to remove reference while view will get dissapear
to remove pull to refresh put this code in to your
override func viewDidDisappear(_ animated: Bool)
override func viewDidDisappear(_ animated: Bool) {
table.dg_removePullToRefresh()
}
And it will looks like
Happy coding :)
You can achieve this by using few lines of code. So why you are going to stuck in third party library or UI.
Pull to refresh is built in iOS. You could do this in swift like
var pullControl = UIRefreshControl()
override func viewDidLoad() {
super.viewDidLoad()
pullControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
pullControl.addTarget(self, action: #selector(pulledRefreshControl(_:)), for: UIControl.Event.valueChanged)
tableView.addSubview(pullControl) // not required when using UITableViewController
}
#objc func pulledRefreshControl(sender:AnyObject) {
// Code to refresh table view
}
you can use this subclass of tableView:
import UIKit
protocol PullToRefreshTableViewDelegate : class {
func tableViewDidStartRefreshing(tableView: PullToRefreshTableView)
}
class PullToRefreshTableView: UITableView {
#IBOutlet weak var pullToRefreshDelegate: AnyObject?
private var refreshControl: UIRefreshControl!
private var isFirstLoad = true
override func willMoveToSuperview(newSuperview: UIView?) {
super.willMoveToSuperview(newSuperview)
if (isFirstLoad) {
addRefreshControl()
isFirstLoad = false
}
}
private func addRefreshControl() {
refreshControl = UIRefreshControl()
refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl.addTarget(self, action: "refresh", forControlEvents: .ValueChanged)
self.addSubview(refreshControl)
}
#objc private func refresh() {
(pullToRefreshDelegate as? PullToRefreshTableViewDelegate)?.tableViewDidStartRefreshing(self)
}
func endRefreshing() {
refreshControl.endRefreshing()
}
}
1 - in interface builder change the class of your tableView to PullToRefreshTableView or create a PullToRefreshTableView programmatically
2 - implement the PullToRefreshTableViewDelegate in your view controller
3 - tableViewDidStartRefreshing(tableView: PullToRefreshTableView) will be called in your view controller when the table view starts refreshing
4 - call yourTableView.endRefreshing() to finish the refreshing
This is how I made it work using Xcode 7.2 which I think is a major bug. I'm using it inside my UITableViewController inside my viewWillAppear
refreshControl = UIRefreshControl()
refreshControl!.addTarget(self, action: "configureMessages", forControlEvents: .ValueChanged)
refreshControl!.beginRefreshing()
configureMessages()
func configureMessages() {
// configuring messages logic here
self.refreshControl!.endRefreshing()
}
As you can see, I literally have to call the configureMessage() method after setting up my UIRefreshControl then after that, subsequent refreshes will work fine.
For 2023, this simple
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
your data source = latest ...
table.reloadData()
table.refreshControl = UIRefreshControl()
table.refreshControl?.addTarget(self,
action: #selector(pulldown), for: .valueChanged)
table.refreshControl?.tintColor = .clear
}
#objc func pulldown() {
your data source = latest ...
table.reloadData()
DispatchQueue.main.async { self.table.refreshControl?.endRefreshing() }
}
Very often, you don't want the "spinner". This line is the easiest way to hide the spinner:
table.refreshControl?.tintColor = .clear
That's it.
If (for some obscure reason) you truly want to subclass UIRefreshControl, there's an excellent recent answer here that shows how to do that.
Others Answers Are Correct But for More Detail check this Post Pull to Refresh
Enable refreshing in Storyboard
When you’re working with a UITableViewController, the solution is fairly simple: First, Select the table view controller in your storyboard, open the attributes inspector, and enable refreshing:
A UITableViewController comes outfitted with a reference to a UIRefreshControl out of the box. You simply need to wire up a few things to initiate and complete the refresh when the user pulls down.
Override viewDidLoad()
In your override of viewDidLoad(), add a target to handle the refresh as follows:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.refreshControl?.addTarget(self, action: "handleRefresh:", forControlEvents: UIControlEvents.ValueChanged)
}
Since I’ve specified “handleRefresh:” (note the colon!) as the
action argument, I need to define a function in this
UITableViewController class with the same name. Additionally, the
function should take one argument
We’d like this action to be called for the UIControlEvent called
ValueChanged
Don't forget to call refreshControl.endRefreshing()
For more information Please go to mention Link and all credit goes to that post
Due to less customisability, code duplication and bugs which come with pull to refresh control, I created a library PullToRefreshDSL which uses DSL pattern just like SnapKit
// You only have to add the callback, rest is taken care of
tableView.ptr.headerCallback = { [weak self] in // weakify self to avoid strong reference
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) { // your network call
self?.tableView.ptr.isLoadingHeader = false // setting false will hide the view
}
}
You only have to add magical keyword ptr after any UIScrollView subclass i.e. UITableView/UICollectionView
You dont have to download the library, you can explore and modify the source code, I am just pointing towards a possible implementation of pull to refresh for iOS

Resources