How do I adjust a UIRefreshControl's position in my view hierarchy? - ios

I have a custom UIRefreshControl "pull to refresh" and for some reason, when I start to scroll, the view appears above all other views, instead of gradually showing itself as the scroll gets dragged down.. this is what I mean:
Now I've attempted to patch this up by making the background and tint colors clear in the viewDidLoad:
var refreshControl: UIRefreshControl!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
refreshControl = UIRefreshControl()
refreshControl.backgroundColor = UIColor.clearColor()
refreshControl.tintColor = UIColor.clearColor()
collectionView?.addSubview(refreshControl)
}
While this does fix the issue of the refresh just being plastered over everything, regardless of its transparency, the refreshControl is still on top of my view hierarchy, you can see when I release the scroll, the letters are still hanging over the view.. And while it does hide the view, when you begin to drag the scroll, it glitches really fast showing the view again:
Now I've even tried calling the sendSubviewToBack and insertSubview:aboveSubview: methods but they don't do anything.. Keep in mind this is a collectionViewController and not a UIViewController with a collectionView on top, so I guess there isn't a view behind the top view which i can send something to the back.. but is there any logic i can implement that will adjust the views bounds when the scrolling begins?

Try this:
refreshControl.layer.zPosition = -1
Also, be careful about using UIRefreshControl without attaching it to an actual UITableViewController. You may see odd artifacts like stuttering (even attaching it to just a UITableView will cause errors). See:
UIRefreshControl without UITableViewController

Related

Custom UIRefreshControl content not showing gradually

I have created a custom view and adding it as a subview inside my scroll view's refresh control, like so:
private func configureRefreshControl() {
scrollView.refreshControl = UIRefreshControl()
let refreshContents = BaseRefreshControl.loadFromNib()
refreshContents.frame = scrollView.refreshControl!.bounds
self.refreshControl = refreshContents
scrollView.refreshControl?.tintColor = .clear
scrollView.refreshControl?.backgroundColor = .blue
scrollView.refreshControl?.addSubview(refreshContents)
}
My BaseRefreshControl is basically a .xib file containing a UIView and a UILabel centered in that UIView.
As you may know, when you pull down your scrollView to refresh the content, it gradually opens (much like a slow animation). However, what I found to be strange is that whenever I pull down to refresh, my UILabel is showing before the center part of the UIView has shown.
So what I have is something like this, if I may:
---------------------------
-- --
-- --------------------- --
-- MyLabel --
-- --
The upper part is my UIRefreshControl which has gradually started showing. 'MyLabel' is showing and overlapping elements of the View Controller underneath. I want the label to appear only when that part of the UIRefreshControl has shown, only when the UIView has completely appeared.
Seems like the solution is simply:
scrollView.refreshControl?.clipsToBounds = true
Hope this helps someone in the future!

UIActivityIndicatorView in the same hierarchy of UITableView in UITableViewController

I have a UIViewController and I dropped a UITableView and UIActivityIndicatorView in the same hierarchy and it's working fine.
But then I have a UITableViewController, with a UITableView of course, and I try to drop a UIActivityIndicatorView at the same hierarchy as the Table View but with no luck. I know that there's a problem with UITableViewController and UIViewController with Table View but how to solve?
These two screenshots will help to understand the problem.
The way I want it to be:
The way it turns out:
The problem is that with the UIViewController it has a self.view as the root view where you can add any sub-items as you did , put you can't do this at least in IB with UITableViewController as the root view is the table itself
First way
add it in code and control is position as the tableView scrolls so change it's frame in scrollViewDidScroll so it's stay at center of screen during the loading
Second way
add it to the main window of the app and remove it when loading finishes
Ok, guys I found one more solution.
Define a variable inside UITableViewController
weak var spinner: UIActivityIndicatorView!
then in viewDidLoad method make this (hope the code is self explanatory)
let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
tableView.backgroundView = activityIndicatorView
tableView.separatorStyle = .none
self.spinner = activityIndicatorView
and then call spinner.startAnimating() when you need to show it, and
self.spinner.stopAnimating()
self.tableView.separatorStyle = .singleLine
to remove it when you don't need it.
When you drag and drop any view (no matter if it is UILabel or UIActivityIndicatorView or UIView),
if it is above the cell you put there, it will be treated as the TableViewHeader automatically.
if it is below the cell you put there, it will be treated as the TableViewFooter automatically.
That is the reason why they are at the same level of view hierarchy of cells.
If you really want to use the indicatorView at same level as your tableView in your tableViewController, even if you programmatically create it and add it to your tableviewcontroller's self.view, it WILL NOT work, because tableViewController's self.view is its content view, which is scrollable anyways, which is kinda a bummer. We only have full control in UIViewController.

Refresh Controller with navigationBar that has prefersLargeTitles set to true

I am using a navigation controller and within that navigation controller, I have a VC with a tableview that is hugging the top, bottom, left and right sides of the superView. When I have self.navigationController?.navigationBar.prefersLargeTitles set to false, everything works fine. But when I set it to true, I have to drag really really far down to get my refresh controller to trigger the refresh. I have to drag so far down that I have to use two fingers on the screen to drag down. Am I doing something wrong here or is this an issue with Apple? Here is how I am adding the refresh controller. I didn't include the handleRefresh function because the code doesn't even reach that point.
var refreshControl = UIRefreshControl()
override func viewDidLoad() {
super.viewDidLoad()
refreshControl.addTarget(self, action:
#selector(FeedViewController.handleRefresh(_:)), for: UIControlEvents.valueChanged)
refreshControl.tintColor = UIColor.gray
self.tableView.refreshControl = refreshControl
}
I am using swift 4 and iOS 11 with Xcode 9. The cells in my tableview are large - one cell is around 400-500 points so almost the size of the screen.
What happens is, the animator starts to animate but when it gets a certain (the point where it should refresh), it just stops. I have included a screenshot of the point at which it stops. Then when I keep on scrolling down further and further, it does the refresh. But it should have done it much earlier. I have to scroll down to the point where the nav bar stretches to almost the entire length of the screen.
self.tableView.addSubview(refresh)

White line below UIRefreshControl when pulled.. tableView

I am implementing a very basic Refresh control...
var refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: Selector(("refresh:")), for: UIControlEvents.valueChanged)
refreshControl.backgroundColor = UIColor.red
self.tableView.addSubview(refreshControl)
for some reason though whenever I pull down to refresh it's like the refresh control cannot keep up with the table view and there is a white gap between the two.
here is the problem on the simulator... it is worse on the iPhone I believe
http://gph.is/2ijyH26
While I also suggest you should do what Artem posted, it didn't directly remove that gap for me. Turns out I was running into 2 different issues related to UIRefreshControl and found a few 'hacks' to get around them. The easy fix is to set the tableView's background colour to the same colour as your refresh control, but that has the side effect of seeing the same colour at the bottom of your table view.
My set up is a UINavigationController hosting a UIViewController with a UITableView subview and a UISearchBar set as the tableView's tableHeaderView. My goal is to match the colour of the navigation bar, refresh control, and the search bar.
Issue 1
I think this the same issue that you're seeing. It seems like as soon as you drag to start the pull to refresh action, there's a gap that appears during the first few pts of the gesture. After some threshold, the refresh control becomes the correct colour. On the way back to the table view's resting scroll state though, we see that same gap again just before it reaches a content offset of 0. Here's what that looks like:
Solution 1
If you subclass UIRefreshControl and override the frame and isHidden properties and just print out their values when they are set, you'll notice that the refresh control doesn't actually get unhidden until the distance from the top of the table view is 4pt. Similarly, when you scroll back down you'll also see that it is set to hidden around the same spot, which is before you can't visibly see the refresh control anymore and why we see the gap peeking to the tableView background.
In our subclass, we can prevent this 'early' hiding and 'late' unhiding by overriding the setter and getter of isHidden and the didSet of frame to only hide when the refresh control's offset is actually 0.
class RefreshControl: UIRefreshControl {
override var isHidden: Bool {
get {
return super.isHidden
}
set(hiding) {
if hiding {
guard frame.origin.y >= 0 else { return }
super.isHidden = hiding
} else {
guard frame.origin.y < 0 else { return }
super.isHidden = hiding
}
}
}
override var frame: CGRect {
didSet {
if frame.origin.y < 0 {
isHidden = false
} else {
isHidden = true
}
}
}
}
I call this a hack because I'm not a huge fan of modifying the existing behaviour of UIRefreshControl, but I haven't found a better way to get around this yet.
Issue 2
When pulling to refresh past that gap threshold in issue 1, it seems like the frame of the UIRefreshControl can't keep up with the search bar. This gives us another kind of gap that follows the search bar around. This is what it looks like:
Solution 2
This lagging gap looks like the frames aren't being updated as fast as our scrolling maybe even animated. Turns out that if we set the frame to itself in somewhere like layoutSubviews, we get the following behaviour:
override func layoutSubviews() {
super.layoutSubviews()
var originalFrame = frame
frame = originalFrame
}
Again, this is pretty hacky, but I haven't found a different way to go about this either.
Result
Why do you add refreshControl as a subview? You must do that:
refreshControl.addTarget(self, action: #selector(refresh), for: .valueChanged)
tableView.refreshControl = refreshControl

UIScrollView with UIView (Swift)

I have implemented a UIScrollView with a UIView which I add when viewDidLoad() to the UIScrollView which is set to the UIViewControllers view. When I do this how ever the frame of the UIView with the setTranslatesAutoresizingMaskIntoConstraints(false) gets set to -101.0. This does not happen to another view that is displayed differently,but only happens to this view which is designed the same, and displayed with pushViewController from the navigationController.
The constraints are setup from the NIB/XIB files and I am confused why this is occurring.
Another thing to note is that, when this happens, no matter where I try and change the frame of the UIView, it has no affect.
EDIT:
CODE for viewDidLoad():
override func viewDidLoad(){
// call the super implementation
super.viewDidLoad();
// load our scrollview from our nib file
customScrollView = CustomScrollView.loadFromNib();
// set the resizeing mask to fill screen
customScrollView!.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
// load our uiview from our nib file
containerView = ContainerView.loadFromNib();
// we handle the constraint changes
containerView!.setTranslatesAutoresizingMaskIntoConstraints(false);
customScrollView!.addSubview(containerView!);
// intialize our refresh control
refreshControl = UIRefreshControl();
refreshControl!.tintColor = UIColor.whiteColor();
refreshControl!.addTarget(self, action: "onRefresh", forControlEvents: UIControlEvents.ValueChanged);
containerView!.addSubview(refreshControl!);
// add the view to our controller here
view = customScrollView!;
}
The answer ended up being a custom tableview. If anyone else is having this problem, I highly recommend ditching UIScrollView, because it is an utter disappointment from apple. The documentation is poor, and there are technical issues with it known to be true. There are also technical document notes for some issues on the class for anyone who is trying to do something with the class, be sure to read those.

Resources