I am trying to show a refresh control right when the view loads to show that I am getting getting data from Parse. The refresh control works as it should when the app is running, but I cannot get it to fire programmatically from anywhere in my app.
This is the code that does not appear to run:
override func viewDidAppear(animated: Bool) {
self.refresher.beginRefreshing()
}
Not only does this not run, but having the code in the app changes the attributes of the refresh control. When I have this code in the app, and show the refresher from user interaction, the refresh control does not have its attributed title as it usually does nor does it run the code that it should.
I needed to add a delay between setContentOffset and the self.refreshControl?.sendActions(for: .valueChanged) call. Without the delay, the refreshControl's attributedTitle would not render.
This works in iOS 10 (Swift 3):
self.tableView.setContentOffset(CGPoint(x:0, y:self.tableView.contentOffset.y - (self.refreshControl!.frame.size.height)), animated: true)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: {
self.refreshControl?.sendActions(for: .valueChanged)
})
Here's an extension working on 10.3:
extension UIRefreshControl {
func refreshManually() {
if let scrollView = superview as? UIScrollView {
scrollView.setContentOffset(CGPoint(x: 0, y: scrollView.contentOffset.y - frame.height), animated: false)
}
beginRefreshing()
sendActions(for: .valueChanged)
}
}
Related
I have a UITableViewController and I want to add the feature pull to refresh.
This is easy to add in the code, the problem is that I want to add a extra space during the load time and when this time over return to top and I found the next problem:
If I touch the screen while the view is moving to top, the scroll stop and don't return to top.
I have a example code to reproduce the problem
#IBAction func refreshControlValueChanged(_ sender: UIRefreshControl) {
let elementsCount = numbersElements.count
let offset = self.tableView.contentOffset.y - (self.refreshControl?.frame.height)!
self.tableView.setContentOffset(CGPoint(x: 0, y: offset), animated: false)
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
self.numbersElements.append(String(elementsCount + 1))
self.tableView.reloadData()
sender.endRefreshing()
}
}
I see that I can disable user iteration but I think that is not good fix...
Is there a solution I can use to fix this problem?
Regards and Thanks!
A.
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 the following code for a button in my application to test my implementation of NVActivityIndicatorView:
#IBAction func goButtonPressed(_ sender: Any) {
self.startAnimatingActivityIndicator()
sleep(2)
self.stopAnimatingActivityIndicator()
}
The view controllers in my application also have this extension:
extension UIViewController: NVActivityIndicatorViewable {
func startAnimatingActivityIndicator() {
let width = self.view.bounds.width / 3
let height = width
let size = CGSize(width: width, height: height)
startAnimating(size, message: "Loading...", type: NVActivityIndicatorType.circleStrokeSpin)
}
func stopAnimatingActivityIndicator() {
self.stopAnimating()
}
}
The loading animations work elsewhere in the same view controller (i.e., the viewDidLoad() function) but for some reason I'm unable to get the loading animation to work on this button. The button is connected correctly as the application does sleep for the appropriate amount of time, but the loading animations fail to run.
Thanks in advance for the help!
The Indicator won't spin because the main thread is asleep. Use a 2 second timer to turn off the spinning instead.
#FryAnEgg coming in with the solution! The sleep(2) was preventing the loading animations from running.
Here's my updated code:
#IBAction func goButtonPressed(_ sender: Any) {
self.startAnimatingActivityIndicator()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
self.stopAnimatingActivityIndicator()
}
}
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
I have implemented an horizontal UICollectionView which scrolls automatically to the next cell after 3 seconds.
Everything works as expected, but I have ran into an issue.
Since the function fires automatically after 3 seconds, with a NSTimer it colides with the user's scroll if both occur at the same time.
Example:
User is scrolling left (backwards) at the same time the 3 second function fires, which will scroll to the right (next cell).
I am wondering if there's a way to let's say, disable the automatic scroll if the user himself is scrolling, and re-enable it after a few seconds, if the user stopped scrolling.
Here's my code:
override func viewDidLoad() {
super.viewDidLoad()
startTimer()
}
fileprivate func startTimer() {
Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(scrollToNextCell), userInfo: nil, repeats: true)
}
#objc fileprivate func scrollToNextCell() {
let cellSize = CGSize(width: width, height: height)
let contentOffset = collectionView.contentOffset
if collectionView.contentSize.width <= collectionView.contentOffset.x + cellSize.width {
let rect = CGRect(x: 0, y: 0, width: cellSize.width, height: cellSize.height)
collectionView.scrollRectToVisible(rect, animated: false)
} else {
let rect = CGRect(x: contentOffset.x + cellSize.width, y: 0, width: cellSize.width, height: cellSize.height)
collectionView.scrollRectToVisible(rect, animated: true)
}
}
What's the best approach here?
Something along:
Collection view detects user scroll it disables the startTimer() function, then if the user stops scrolling, fires another function that waits Xn seconds to fire the startTimer() function again.
Then every time the user scrolls again, it disables again the startTimer() and the new function that enables the startTimer() if it was running.
How can I achieve this?
Make use of the UIScrollViewDelegate
// called on finger up as we are moving
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
Invalidate Timer.
}
// called when scroll view grinds to a halt
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
Restart Timer.
}
Alternative Approach:
Make use if deceleratingproperty. It returns YES if user isn't dragging (touch up) but scroll view is still moving.
#objc fileprivate func scrollToNextCell() {
if (!collectionView.isDecelerating) {
//your code here
}
}