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)
Related
I have a messaging application, the problem is that the first messages are not visible, when I drag the collection view to see the top messages I can see them but when I release the finger, the collection view jumps back, hiding the first 5 messages that are at the top of the screen
As we can see from the image there is a message (actually are 5 messages at the top), however I have to drag the collection view to peek those messages. I thought that the size of the collection view is actually larger that the screen, however both the collectionView.frame.height and the UIScreen.main.bounds.height have the same height, is that ok? .
Here's the code that I use to setup the collection view:
/// Function that configures the ChatViewController collection view
private func configureCollectionView() {
collectionView?.backgroundColor = Theme.current.chatGeneralBackground
collectionView?.alwaysBounceVertical = true
collectionView?.keyboardDismissMode = .onDrag
// Register the chat cell and the loading cellx`
collectionView?.register(ChatCell.self, forCellWithReuseIdentifier: chatCellIdentifier)
collectionView?.register(LoadingCollectionViewCell.self, forCellWithReuseIdentifier: loadingCellIdentifier)
// Initialize the original height of the collection view
collectionViewOriginalHeight = collectionView.frame.height
}
What am I doing wrong?
As of iOS 7.0, all views automatically go behind navigation bars, toolbars and tab bars to provide what Apple calls "context".
For example, if you don't want a view controller to go behind any bars, use this in viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.edgesForExtendedLayout = []
}
It seems to be the problem with constraints, if you are using custom navigation bar top constraints should be in reference to it. If using default navigation controller, you might need to give the top constraint to 44 from safe area.
However UIScreen.main.bounds.height consists of complete screen including the top navigation bar and 20 px of status bar. For collectionView.frame.height you need to minus these values.
What I want to to: I want to drag down the whole view of a viewController to dismiss to the parent viewController using a pan gesture recognizer.
The Problem: When I drag the view down, the navigationBar decreases its height and does not look good. When the view returns to its original position, the navigationBar returns to the default size. I want the navigationBar to stay at its size. I also tried to use the new large titles and some other properties of the navigationController/-bar, but that did not solve it.
Note: Everything worked fine already before iOS 11.
My code:
override func viewDidLoad() {
super.viewDidLoad()
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(dragViewDown(_:)))
navigationController!.view.addGestureRecognizer(panGesture)
}
#IBAction func dragViewDown(_ gesture: UIPanGestureRecognizer) {
if let dragView = gesture.view {
let translation = gesture.translation(in: dragView)
dragView.center.y = (dragView.center.y + translation.y)
gesture.setTranslation(CGPoint.zero, in: dragView)
}
}
This test project only has one viewController and does not provide the dismissal, but the problem is the same as in my working project.
I also uploaded the project to GitHub: https://github.com/maddinK7/navitationBar-pull-down-problem
Does anyone have an idea how to solve this? Thanks in advance.
I want the navigationBar to stay at its size
It is staying at its size. If you check the navigation bar's bounds size height before, during, and after the drag, you will see that it remains the same (probably 44) at all times. What's changing is the drawing extension that causes the drawing of the nav bar to extend up behind the status bar. It can't do that when you pull the whole thing away from the top of the screen, because it is not at the top next to the status bar any more. iOS 11 is more strict about the way it performs this drawing extension, probably because it has to do it in a special way on the iPhone X.
So, let's make sure you're doing this correctly:
Make sure that the navigation bar has a top constraint pinned to the safe area layout guide's top, with a constant of zero.
Make sure that the navigation bar has a delegate that returns .topAttached from position(forBar:).
If you are doing both those things and it doesn't help, you'll have to implement this in some other way entirely. Making the view directly draggable like this, without a custom parent view controller, was always dubious.
When UINavigationController attached top, system will add safe area top margin in the navigation background.
(NOTICE: Background margin will not changed when offset value is between 1 and 0)
So you have to handle attached/detached top event by handle gesture offset to change the right offset and content insets.
You can try the solution in my lib example. ;)
My example include UITableViewController in the UINavigationController, so it will relatively complex.
https://github.com/showang/OverlayModalViewController
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
I've looked around and tried everything I can think of sort of subclassing TableView, but I think I'm missing something. I have a TableView with entries that I can swipe left and right on, and everything works fine. However, if:
1) I start (vertically) scrolling a little bit, and then swipe, the TableView's superclass, ScrollView, seems to block the swipe from my TableViewCell.
2) I stop scrolling, but the animation hasn't fully stopped, the swipe is still blocked from the TableViewCell.
How can I allow my swipe to get passed through to my TableViewCell, regardless of the vertical scroll?
Swift 2.2, Xcode 7.3
I fixed this basically by doing what was suggested in this thread: Tell ScrollView to Scroll after other pan gesture
Below is code that should enable someone else that comes across this thread to scroll in a table view, and be able to swipe, without having to deal with the table view's pan gesture recognizer blocking the swipe due to the mere hint of vertical motion.
Hope it helps someone.
So (inside a UITableViewController -- non--essential code emitted):
var lsgr : UISwipeGestureRecognizer!
override func viewDidLoad(){
super.viewDidLoad()
self.lsgr = UISwipeGestureRecognizer(target: self, action: "didSwipeLeft:")
self.lsgr.direction = .Left
self.lsgr.cancelsTouchesInView = false
self.lsgr.delegate = self
}
func didSwipeLeft(leftSwipe: UISwipeGestureRecognizer){
var ip = self.tableView.indexPathForRowAtPoint(leftSwipe.locationOfTouch(0,inView: self.tableView))
print("swipe left - "+String(ip!.row))
}
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