I have a table in view "A" that is inside a tab bar controller. When I scroll down to reload the table refresh control start animating.When press tab bar controller going in view "B" and later go back to view "A", refresh control is visible but isn't animating.
Can someone help me? thank
func refresh(sender:AnyObject)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { () -> Void in
Call API for data
dispatch_async(dispatch_get_main_queue(), { () -> Void in
UPDATE UI
refreshControl.endRefreshing()
})
});
}
override func viewDidAppear(animated: Bool)
{
if(requestTerminated == false)
{
//Continue animate refresh control somehow
}
}
Suppose you have this situation in your controller A , for example in viewDidLoad:
//global var
var refreshControl:UIRefreshControl!
self.refreshControl = UIRefreshControl()
self.refreshControl.attributedTitle = NSAttributedString(string: "pull to refresh")
self.refreshControl.addTarget(self, action: #selector(MyTableViewController.pullToRefresh(_:)), forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(refreshControl)
As can you see you have your refreshControl add as a subview to your A table controller. When you go to another controller probably you present another controller (B) and when you come back to A your refresh is freezed or stucked.
You could start UIRefreshControl animation on viewWillAppear and end it on viewDidDisappear. During transition save the state of refresh process to know when to show UIRefreshControl.
You can use a boolean like this:
// Refresh state
var isRefreshing = false
// Call on viewWillApper
func superviewWillApper(){
if isRefreshing && !refreshControl.refreshing{
startRefreshAnimation()
}
}
// Call on viewDidDisapper
func superviewDidDisappear(){
endRefreshAnimation(false, dataFetched: !isRefreshing)
}
func startRefreshAnimation(){
refreshControl.beginRefreshing()
contentOffset = CGPointMake(0, -refreshControl.bounds.size.height)
isRefreshing = true
}
//Saves state of refresh
func endRefreshAnimation(wasEmpty: Bool, dataFetched: Bool){
refreshControl.endRefreshing()
isRefreshing = !dataFetched
if !wasEmpty{
setContentOffset(CGPointZero, animated: true)
}else{
setContentOffset(CGPointZero, animated: false)
}
}
Related
I'm trying to add pull to refresh on my wkwebview app. I'm using UIViewRepresentable for my webview, so I don't have the onViewLoad function and view controllers. Here's my code:
var wbWebViewEl: WKWebView = WKWebView()
struct SwiftUiWebView: UIViewRepresentable {
....
class refreshWebViewClass {
#objc func refreshWebView1(sender: UIRefreshControl) {
print("test debug :D")
wbWebViewEl.reload()
DispatchQueue.main.async {
sender.endRefreshing()
}
}
func makeUIView(context: Context) -> WKWebView {
......
wbWebViewEl.scrollView.bounces = true
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(refreshWebViewClass.refreshWebView1), for: UIControl.Event.valueChanged)
wbWebViewEl.scrollView.addSubview(refreshControl)
wbWebViewEl.scrollView.refreshControl = refreshControl
.....
}
.....
}
At the moment, when you swipe down, it shows the refresh loading spinner, but it doesn't stop spinning. Also the test print I put in the refreshWebView1 function, doesn't show in the logcat. Does anyone know what I'm getting wrong here? If you need to see anymore code let me know :)
Looks like your refreshWebViewClass should be a Coordinator. Then, in your addTarget, the target would be context.coordinator, not self, which doesn't make sense here, since self doesn't actually implement this method.
I have a tableview with refresh control:
tableView.refreshControl = UIRefreshControl()
tableView.refreshControl?.addTarget(self, action: #selector(refresh), for: .valueChanged)
And I have a custom SkeletonView which replaced by tableview when user pull to refresh:
private func setLoading(_ loading: Bool) {
if loading {
tableView.isHidden = true
tableView.scrollToTop(animated: false)
setSkeletonView(isHidden: false)
} else {
setSkeletonView(isHidden: true)
tableView.isHidden = false
}
}
When I am pulling my refresh control it disseapears with a tableview and appears after successful loading. Also, I don't want to set a largeTitle navbar, I want to use a small one.
Is it possible to be a refresh control unhidden if tableview is hidden?
Basically no. If B is a subview of A then if A is hidden, B is hidden.
When i want to the update data source of the tableview, firstly i want to scroll to top (to header view) then show the UIRefreshControl view, after data source updated then i want to hide the UIRefreshControll.
To do that i tried following line:
self.kisilerTable.scrollRectToVisible(CGRect(x: 0, y: 0, width: 1, height: 1), animated: false)
self.kisilerTable.setContentOffset(CGPoint(x: 0, y: self.kisilerTable.contentOffset.y - (self.refreshControl.frame.size.height)), animated: true)
self.kisilerTable.refreshControl?.beginRefreshing()
Above code is scrolling to top then it shows the UIRefreshControl. And after my data updated i tried to hide it with following code:
self.kisilerTable.reloadData()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.kisilerTable.refreshControl?.endRefreshing()
}
But the problem is, above code is hiding the indicator animation but it leaves a space there. As you can see in the screenshot:
If i try to hide with following code:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.kisilerTable.reloadData()
self.kisilerTable.refreshControl?.endRefreshing()
}
It is hiding but, i am losing the hide animation. I don't want to lose hide animation.
How can i resolve this problem?
I think it happens, because in the beginning you use setContentOffset to leave a space, but that space is not removed.
I will suggest another implementation, which works very well:
First you declare the refreshControll before the method viewDidLoad():
let refreshControl = UIRefreshControl()
Now do you need create a method for stop the animation when needed:
func stopAnimatingRefreshControl() {
if let refreshControl =
tableView.subviews.first(where: { $0 is UIRefreshControl})as? UIRefreshControl,
refreshControl.isRefreshing {
refreshControl.endRefreshing()
}
}
Now you create an #objc function that be called when scrolls the table to update, normally in this moment you call methods that make your server request to update data.
When this data is updated, you call the function stopAnimatingRefreshControl():
#objc func refreshMyData() {
// call methods for get data from server
getDataFromServer()
}
func getDataFromServer() {
// request completed and data is updated, now i need stop the loading.
DispatchQueue.main.async {
self.tableView.reloadData()
}
stopAnimatingRefreshControl()
}
Finally, after creating the necessary methods, you need to link our #objc function to refreshControll and our tableView, do it in method viewDidLoad() :
refreshControl.addTarget(self, action: #selector(refreshMyData), for: .valueChanged)
tableView.addSubview(refreshControl)
Now you're done, I hope I helped.
I have a UITableView, that on the delegate of the UIViewController that adds a new item, inserts the row. The row gets correctly inserted.
However, the UIRefreshControl on the UITableView will not go away when dragging the UITableView after that.
extension FeedViewController: AddPostDelegate {
func didAddPost(_ userPost: UserPost) {
self.postsWrapper?.posts.insert(PostWrapper(type: PostType.user(userPost)), at: 0)
self.tableView.beginUpdates()
self.tableView.insertRows(at: [
IndexPath(row: 1, section: 0)
], with: .automatic)
self.tableView.endUpdates()
self.refresher.endRefreshing()//Does nothing
}
}
In viewDidLoad:
refresher = UIRefreshControl()
tableView.addSubview(refresher)
refresher.addTarget(self, action: #selector(didDragScrollView), for: .valueChanged)
refresher.layer.zPosition = -1//otherwise in front of the header cell when refreshing (iOS 9.2)
If I do not go to another view first than come back before attempting to pull for refresh, it always hangs spinning forever.
EDIT:
It looks like the UIRefreshControl is no longer calling the target function AFTER I add a post.
Any ideas on why this may be occurring? How to fix that?
Make sure that beginRefreshing() was not called before the user pulled to refresh. If refreshControl.isRefreshing is true then the selector does not get called upon "pull to refresh".
A simple test:
let refreshControl = UIRefreshControl()
override func viewDidLoad() {
super.viewDidLoad()
refreshControl.addTarget(self, action: #selector(ViewController.refreshData), for: .valueChanged)
tableView.refreshControl = refreshControl
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// sets refreshControl.isRefreshing to true
// before the user initiates it
refreshControl.beginRefreshing()
}
Then the user pulls to refresh. Because refreshControl.isRefreshing is true the selector will not get called. If you remove refreshControl.beginRefreshing()in viewDidAppear, the selector will get called:
#objc func refreshData() {
print("refresh initiated")
refreshControl.endRefreshing()
}
Try assigning your UIRefreshControl into the refreshControl property of UITableView like this:
if #available(iOS 10.0, *) {
tableView.refreshControl = refresher
} else {
tableView.addSubview(refresher)
}
Note that this requires iOS 10.0 or higher, but we're soon getting iOS 12.0 so I don't think you should support iOS lower than 10.0
My activity indicator seems not disappear after I go back page. For the first time, it's disappear. Then I go to second view controller then go back to the first view controller. The activity indicator supposedly not to showing up since all UIViews already appeared.
My code
var activityView : UIActivityIndicatorView = UIActivityIndicatorView(frame: CGRectMake(0,0, 50, 50)) as UIActivityIndicatorView
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(.POST , "192.168.0.1/test", parameters: [])
.responseJSON { response in
if response.result.isSuccess {
var jsonObj = JSON(response.result.value!)
print(jsonObj)
self.activityView.stopAnimating()
}
}
}
override func viewDidAppear(animated: Bool) {
activityView.center = self.view.center
activityView.hidesWhenStopped = true
activityView.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
view.addSubview(activityView)
activityView.startAnimating()
}
You are showing your activityIndicator in viewDidAppear. That is why it appears when you go back from second to first. Also have a look at view life cycle. viewDidLoad is only called once whenever view controller is initialized, but viewDidAppear will be called when ever the view controller is presented. (either back navigation or presented again)
So its better to add views in viewDidLoad and activity indicator should always be associated with its network call. Before starting the call show the activity indicator and hide or remove once its done.
Also, you have missed super calls. Be sure to always call super methods when overriding.
Ex:
override func viewDidLoad() {
super.viewDidLoad()
addActivityIndicator()
fetchRemoteData()
}
func addActivityIndicator() {
activityView.center = self.view.center
activityView.hidesWhenStopped = true
activityView.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
view.addSubview(activityView)
}
func fetchRemoteData() {
activityView.startAnimating()
Alamofire.request(.POST , "192.168.0.1/test", parameters: [])
.responseJSON { response in
if response.result.isSuccess {
var jsonObj = JSON(response.result.value!)
print(jsonObj)
self.activityView.stopAnimating()
}
}
}
U want to hide and show your activity indicator when you want. And also make sure to set as bringSubviewToFront