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!
Related
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)
The end goal is to create the same scrolling effect as the one on the "Me" tab of the twitter iOS app where the segmented control rises to the top as you scroll down and then stays fixed to it unless scrolled back up to the top.
The solution I've come up with is illustrated below. There is a view at the top, a segmented control, and a table view beneath the segmented control. All of these elements are embedded inside a scrollview that takes up the entire screen (minus tab & nav bars).
Here is the key issue: If begin scrolling by swiping up from the top-most view or the segmented control, it scrolls the scrollview that all the elements are embedded in. If I scroll the tableview, it will only scroll itself and leave the top-most view and segmented control unaffected.
How can I scroll the scrollview that the elements are embedded in no matter where the scrolling occurs on screen?
I had a similar layout in one of my projects. I used SJSegmentedViewController.
It Requires a headerViewController ,datasource for middle segment and viewController array for those segments.
This library allows you to scroll from anywhere on the screen moreover the segmented control sticks to the top as user scrolls all the way to top.
Here is how you can implement this :
First import the module into your class
import SJSegmentedScrollView
Then create a headerViewController and two viewControllers(Say Video and Tips) for segment
let headerViewController = HeaderViewController()
let video = VideoController()
let tips = TipsController()
After that set these Controller and also set the title for segmented control as following:
segmentController.headerViewController = header
segmentController.segmentControllers = [video,tips]
video.title = "Video"
tips.title = "Tips"
Then add it to the Container View
addChildViewController(segmentController)
containerView.addSubview(segmentController.view)
segmentController.view.frame = self.containerView.bounds
segmentController.didMove(toParentViewController: self)
Here Container View is a UIContainerView
Last but make sure to call in child controllers (VideoController,TipsController), After calling this function in those controllers you can scroll from anywhere on the screen.
extension HomeListingViewController: SJSegmentedViewControllerViewSource {
func viewForSegmentControllerToObserveContentOffsetChange() -> UIView {
//Scrollview in child controllers
return scrollview
}
}
You can find the full documentation here
Hope this helps!
The refresh color doesn't match the tint color and looks different, i tryied to change tintAdjustmentMode but the result is the same
Just to note, the spinner and text color should be 0x2C76BE
tvc.refreshControl = [UIRefreshControl new];
tvc.refreshControl.tintAdjustmentMode = UIViewTintAdjustmentModeNormal;
tvc.refreshControl.tintColor = [UIColor colorWithHex:0x2C76BE];
tvc.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:#"Pull to query spectrum again" attributes:#{NSForegroundColorAttributeName:[UIColor colorWithHex:0x2C76BE]}];
I had a similar problem with UIRefreshControl not displaying the color correctly when the view loads and I call beginRefreshing(). If the user pulls to refresh, the control correctly displays the tintColor I've specified.
First, subclass the refresh control. Then, override the didMoveToWindow method in the subclass. The following code finds the element that's animated to create the spinner and sets its background color.
This code uses an extension to UIView to return all the view's subviews (I used Jon Willis' answer from Swift: Recursively cycle through all subviews to find a specific class and append to an array).
class CustomRefreshControl: UIRefreshControl {
override func didMoveToWindow() {
super.didMoveToWindow()
if let l = getNestedSubviews().first(where: { $0.layer is CAReplicatorLayer }), l.subviews.count > 0 {
l.subviews[0].backgroundColor = UIColor.orange //getNestedSubviews method is an extension of UIView as referenced above
}
}
The spinner has a CAReplicatorLayer whose view contains one subview. That subview is simply a rectangle implements the graphic element for the spinner. It's that graphic element you're coloring.
UIRefreshControl is a buggy class. I noticed that placing the tvc.refreshControl.tintColor = [UIColor colorWithHex:0x2C76BE]; inside an animation block (even of zero duration) would yield the expected the results. So I tested to do this hideous 'hack': dispatch_async(mainQueue, <#set tintColor#>); and that also give the right result. There might also be a dependency of the refreshcontrol on the the timing of calling -beginRefreshing or -endRefreshing too.
Because I was annoyed so much by UIRefreshControl's buggyness and limitation of only being usable in a UITableViewController, I created a fully customizable one of my own, usable with any type of UIScrollView (UICollectionView, UITableView). Note that I created this before UICollectionViewFlowLayout supported the sticky headers like a tableView, so my refreshcontrol does not work well when that option is on. Feel free to submit a fix ;).
You can find it here https://github.com/Joride/JRTRefreshControl (if this falls under the 'shameless plugging clause' I will remove this link, but I think it is relevant to the question.
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
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.