White line below UIRefreshControl when pulled.. tableView - ios

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

Related

UISegmentedControl Set Selected Index Without Animation

I have a UITabBarController with 3 views. Each view has a UISegmentedControl in its navigation bar, and all of these segmented controls need to remain in sync. Each time the selectedSegmentIndex on one of them changes, I fire an event that causes the others to change their selectedSegmentIndex as well. I also set the index in viewDidLoad to ensure it is correct the first time the view is displayed.
This all works as expected, except that when I switch to a different view I can see the segmented control animate its button to the correct position, even though the selectedSegmentIndex may have been set much earlier.
Can anyone tell me what is going on here? Is there a way to disable this animation (when setting the index programmatically)?
Here is the working solution from the comments:
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
UIView.performWithoutAnimation {
segmentedControl.selectedSegmentIndex = 0
segmentedControl.layoutIfNeeded()
}
}
I know this answer is very late but in case anyone else needs same solution;
Layer of subviews of SegmentedControl has their own animation. Which are;
SelectionScale
SelectionPosition
SelectionBounds
here is a sample code from my project, I wrote whole new class for my UISegmentedControl
let foregroundIndex = numberOfSegments
if subviews.indices.contains(foregroundIndex),
let foregroundImageView = subviews[foregroundIndex] as? UIImageView {
foregroundImageView.layer.removeAllAnimations()
}
this is going to remove all animations over the selected UISegment and there will be no transition, it'll just snap.

How to add accessibility for UIRefreshControl in Swift

I am trying to add accessibility for UIRefreshControl which has been implemented for pull to refresh in UITableView as below
self.refreshControl.accessibilityLabel = "Refreshing"
But if accessibility turned on and by swiping three finger on UITableview then does not speaking as Refreshing.
I override accessibilityScroll method as below.
override func accessibilityScroll(_ direction: UIAccessibilityScrollDirection) -> Bool {
if direction == .up {
self.refreshControl.accessibilityLabel = "Refreshing"
}
return true
}
Any idea Thanks!
Firstly, refreshing is more than just scrolling up. Its scrolling up past y position 0. Otherwise this will happen whenever the user scrolls up. Secondly, use UIAccessibilityPostNotification to announce to the user that a UI element has just changed.

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)

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

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

Why does a button return to its original position when I change its background image?

I have a button which has been placed in the Storyboard on a UIViewController and has a bottom space constraint of -500 attached to it which means it isn't visible in the superView. A function brings it up so that it is visible when it's needed.
The same button has a background image which gets changed by tapping it:
#IBAction func button(sender: AnyObject) {
let image1a = UIImage(named: "NewImage")
button.setImage(image1a, forState: .Selected)
}
However, when the button changes its background it returns to its original position. This means it is no longer visible in the superView. How do I fix this?
My psychic powers tell me you're "bringing it up" by changing the view's frame. You need to modify the constraint constant, or anytime something causes the view to invalidate its layout (such as changing the image, most likely) the layout engine will re-apply the constraints.

Resources