I set a UICollectionView y position outside the screen (set it aligned to the bottom safe area, and offset -154, so it is off screen).
When the app starts, I use a button to move the entire root view up
self.view.center.y -= 154
Both the root view and my UIView appear in expected position, but the UICollectionView can not be tapped. The new zone does not recognize the UIEvent.
I added a gesture to the UICollectionView but it doesn't work. Only the root view zone responds to the UIEvent.
#IBAction func filmRollButton(_ sender: UIButton) {
var toUp = true
if self.toUp == true {
self.view.center.y -= 154
} else {
self.view.center.y += 154
}
self.toUp = !self.toUp
}
// tap - UICollection View appear(y position change)
// tap again - UICollection view disappear
Expected:
Use some method to enable the UICollectionView tap event. (extend root view zone? because only root view zone can be tapped.)
Other simple draw effect like setting the UICollectionView height to 0, then tap button and set the height to 154.
Try using layoutIfNeeded() , to layout subviews right away. Might be pending
#IBAction func filmRollButton(_ sender: UIButton) {
var toUp = true
if self.toUp == true {
self.view.center.y -= 154
self.view.layoutIfNeeded()
} else {
self.view.layoutIfNeeded()
self.view.center.y += 154
}
self.toUp = !self.toUp
}
// tap - UICollection View appear(y position change)
// tap again - UICollection view disappear
Related
I am moving the origin of the self.view by the -minY of the text field when the keyboard shows to make the text field show at top of the screen. But if the text field is near the bottom, the frame bottom gets black color. I tapped on the first text field in the attached image which moves it to top. Is there a way to move the view without displaying black color?
The view hierarchy is
+ UIViewController
+ UIView
+ UIScrollView
+ UIView
- UITextField
- UITextField
- UITextField
self.view.frame.origin.y = -(tagsTextField.frame.minY)
I fixed it by setting scrollview content offset on textfield tap.
scrollView.setContentOffset(CGPoint(x: 0, y: tagsTextField.frame.minY), animated: true)
And resetting on keyboard dismiss.
scrollView.setContentOffset(CGPoint(x: 0.0, y: 0.0), animated: true)
You can move frame up when keyboard frame hides your textfield by the height of keyboard:
override func keyboardWillBeShown(note: Notification) {
let userInfo = note.userInfo
let keyboardFrame = userInfo?[UIKeyboardFrameEndUserInfoKey] as! CGRect
if keyboardFrame.intersects(activeTextField.frame) {
UIView.animate(withDuration: 0.2) {
self.view.frame.origin.y = -keyboardFrame.height
}
}
}
override func keyboardWillBeHidden(note: Notification) {
UIView.animate(withDuration: 0.2) {
self.view.frame.origin.y = 0
}
}
You can set the background color of the embedding view (perhaps your navigation controller) the same color as your view background color.
What I usually do in form screen is I put my input fields in a UITableView. Then, instead of moving the view up to make the current input field visible, I scroll the UITableView.
So, the background view/color of the UITableView does not move.
How can I make the scroll view stop moving when dragging on it, after that, how can I make it start scrolling immediately when finger already touching the scroll view?
self.scrollView.notMovingWhileDragging()
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.scrollView.resumeMovingForTheAlreadyTouchingFinger()
}
For the above two functions, I set isScrollEnabled to false then true, but the scroll view not move if finger already touching it when above callback is called.
The result I want is like the iOS Maps app, when the sheet is on top, and the scroll view is been dragged a little distance from top. Now I start dragging down the scroll view, scroll view move down, when scroll view is been scroll to top, then keep dragging down, the sheet down(scroll view stop moving when dragging), then dragging up, the sheet view up, and when the sheet view is on top, keep dragging up, the scroll view start scrolling again (scroll view resume moving when finger already on it).
It's not possible. The isScrollEnabled property of a UIScrollView simply updates the isEnabled property of the UIScrollView's panGesture. UIPanGesture has a defined lifecycle that begins with touchesBegan as a touch down. and its state moves to .began, so enabling it while a touch has already begun will not cause it to fire.
In your original question you asked how to allow scrolling after handling the positioning of a sheet-style interface a la Apple Maps. This can be done by adding a target your scroll view's panGesture. For example, you can add a target like so tableView.panGestureRecognizer.addTarget(self, action: #selector(handlePan(_:))); then in your action you can say something like:
#objc func handlePan(_ sender: UIPanGestureRecognizer) {
let velocity = sender.velocity(in: view)
let translation = sender.translation(in: view)
switch sender.state {
case .began: break
case .changed:
// shouldMoveSheet can check to see if the sheet should move or scroll view should scroll based on content offset, pan translation, etc.
if shouldMoveSheet(for: translation) {
bottomConstraint.constant = newConstant
// while moving sheet set the content offset to the top so the scroll view does not scroll
tableView.setContentOffset(CGPoint(x: 0.0, y: tableView.adjustedContentInset.top), animated: false)
sender.setTranslation(.zero, in: view)
}
case .ended:
// Depending on if the sheet has been moved when the gesture ends move the sheet to the up or down position
if velocity.y < 0 && tableView.contentOffset.y <= tableView.adjustedContentInset.top {
bottomConstraint.constant = constantForSheetUp
} else if tableView.contentOffset.y <= tableView.adjustedContentInset.top {
bottomConstraint.constant = constantForSheetDown
}
UIView.animate(duration: 0.3) {
view.layoutIfNeeded()
}
case .failed, .cancelled, .possible:
break
}
}
Which will enable a pan up to some threshold then scrolling:
I am making an app that returns venues (like restaurants) in a fashion similar to apple maps, i.e. a table view that is initially hidden mostly off screen and then moves up above other content when the table view's contents are to be displayed.
This is what I've gotten so far:
You'll notice that when pulling the table view back down to reveal the map behind it, the table view does not move down in one fluid motion. In fact, when the table view reaches its "top," you'll notice that the scroll bar on the right side is visible but the view isn't moving. This is because, while recording, I'm dragging downward but the view takes a few moments to actually begin to translate downward. I'll explain why it has this sort of delay, but first compare this behavior to that of the Apple maps table view:
Notice the fluidity in the Apple maps table view's transition from translating the view itself to scrolling the scroll view. A translation upward of the view converts into a scrolling downward of the scroll view (and vice versa) when the view's minY hits a certain point. This is all done throughout one upward swipe by the user.
Now I'll explain what I did to create my translating scroll view. The scroll view itself is a UITableViewController embedded in a container view on the map's view controller.
The view that holds the container view has a pan gesture recognizer. When the pan gesture recognizer's state is UIGestureRecognizerState.ended, i.e. the drag has ended, it checks the container view's minY in relation to a predetermined Y coordinate. If the minY is less than the predetermined Y coordinate, it will animate the container view to snap its minY to that Y coordinate. The case is the same if the minY is slightly greater than the predetermined Y coordinate, in this instance the container view will animate upward so its minY snaps to this predetermined Y coordinate.
Once the view has snapped into this position, the embedded UITableViewController's ".isScrollEnabled" property, which is initially set to false, is now set to true. When the embedded table view is now scroll enabled, a drag executed by the user will scroll the table view as opposed to translating the container view. To undo this, I've made it so that when the table view has reached the top of its scroll, its ".isScrollEnabled" property is set back to false. This means that any dragging motion by the user will translate the view as opposed to scrolling the table view. This does not create the fluidity in transition between scrolling and translating the way Apple maps does. The user would actually have to execute multiple drags on the area in order to transition from scrolling to translating; Apple maps is able to do this in one drag.
Here is the code I used within the container view's parent View controller to:
Snap container view into place
Change .isScrollEnabled property of embedded table view controller to true
#objc func handlePan(sender: UIPanGestureRecognizer) {
let resultView = self.resultView
let translation = sender.translation(in: view)
switch sender.state {
case .began, .changed:
resultView?.center = CGPoint(x: (resultView?.center.x)!, y: (resultView?.center.y)! + translation.y)
sender.setTranslation(CGPoint.zero, in: view)
if (resultView?.frame.minY)! <= self.view.frame.height - self.searchView.frame.height - self.view.safeAreaInsets.bottom && self.resultView.frame.height >= (self.searchView.frame.height + self.view.safeAreaInsets.bottom) {
resultView?.frame.size.height -= translation.y
}
case .ended:
if (resultView?.frame.minY)! > self.view.frame.height * 0.40 {
self.resultTableViewController?.tableView.isScrollEnabled = false
} else {
self.resultTableViewController?.tableView.isScrollEnabled = true
}
if (resultView?.frame.minY)! <= view.frame.height * 0.40 {
let difference = (resultView?.frame.minY)! - (view.frame.height * 0.05)
UIView.animate(withDuration: 0.25) {
resultView?.center = CGPoint(x: (resultView?.center.x)!, y: (resultView?.center.y)! - difference)
resultView?.frame.size.height += difference
self.view.layoutIfNeeded()
}
dismissed = false
} else if (resultView?.frame.minY)! >= view.frame.height * 0.75 {
let difference = (view.frame.height - dismissResultButton.frame.height - view.safeAreaInsets.bottom) - (resultView?.frame.minY)!
UIView.animate(withDuration: 0.25, animations: {
resultView?.center = CGPoint(x: (resultView?.center.x)!, y: (resultView?.center.y)! + difference)
self.resultView.frame.size.height = self.searchView.frame.size.height + self.view.safeAreaInsets.bottom
self.view.layoutIfNeeded()
})
dismissed = true
} else if (resultView?.frame.minY)! >= view.frame.height * 0.40 {
let difference = mapView.frame.height - (resultView?.frame.minY)!
UIView.animate(withDuration: 0.25) {
resultView?.center = CGPoint(x: (resultView?.center.x)!, y: (resultView?.center.y)! + difference)
self.resultView.frame.size.height = self.searchView.frame.size.height + self.view.safeAreaInsets.bottom
self.view.layoutIfNeeded()
}
dismissed = false
}
sender.setTranslation(CGPoint.zero, in: view)
default:
break
}
}
And now, within the UITableViewController:
Ensures that the scroll view only bounces when at the bottom of the scroll (it should lock and then convert to a container translation when reaching the top)
Change .isScrollEnabled back to false and allow container view translation on drag
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollView.bounces = (scrollView.contentOffset.y > 0)
}
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
let translation = scrollView.panGestureRecognizer.translation(in: scrollView.superview!)
if translation.y > 0 {
if scrollView.contentOffset.y == 0 {
tableView.isScrollEnabled = false
}
}
}
This is the best I could come up with to try and replicate this behavior by the Apple maps table view. Any help/suggestions would be appreciated.
P.S. heres the github to the project if you'd like to break it down more: https://github.com/ChopinDavid/What2Do
I have a normal ViewController with a scroll view. In the scroll view are a few views a label and some buttons. Below all of that is a UIView. That UIView has multiple View Controllers being passed into it whenever one of the buttons is pressed. Each View Controller being passed into the UIView is a different height.
A picture to explain:
How do I adjust the Scroll View so it fits the new View Controller being passed in from the button press?
UPDATE
Here's my code:
#IBAction func didPressTab(_ sender: UIButton) {
let previousIndex = selectedIndex
selectedIndex = sender.tag
buttons[previousIndex].isSelected = false
let previousVC = viewControllers[previousIndex]
previousVC.willMove(toParentViewController: nil)
previousVC.view.removeFromSuperview()
previousVC.removeFromParentViewController()
sender.isSelected = true
let vc = viewControllers[selectedIndex]
addChildViewController(vc)
print("1. NEW VIEW HEIGHT \(vc.view.frame.height)")
print("1. SCROLL VIEW HEIGHT \(scrollView.contentSize.height)")
print("1. VIEW CONTROLLER HEIGHT \(self.view.frame.height)")
print("1. CV HEIGHT \(contentView.frame.height)")
contentView.layoutIfNeeded()
height = vc.view.frame.size.height
contentView.translatesAutoresizingMaskIntoConstraints = true
contentView.frame.size.height = vc.view.frame.height
vc.view.frame = contentView.bounds
scrollView.contentSize.height = staticView.frame.height + height!
contentView.addSubview(vc.view)
print("2. NEW VIEW HEIGHT \(vc.view.frame.height)")
print("2. SCROLL VIEW HEIGHT \(scrollView.contentSize.height)")
print("2. VIEW CONTROLLER HEIGHT \(self.view.frame.height)")
print("2. CV HEIGHT \(contentView.frame.height)")
vc.didMove(toParentViewController: self)
}
I am able to get the height to change, but it just ends up making the UIView the size of a normal VC (667) which is not what I need because some of the ViewControllers are only 450 in height.
You must recalculate the height of the scroll view content size whenever the embedded view controller changes.
The new height is image view height + buttons container height + content view height.
Obviously you must adapt the height of your container view too.
I have found the solution!
Preface:
View Controllers have an assumed size. (the size of your phone's screen). That's why whenever I presented a view added a view to the UIView it was always 667 -(iPhone 6 Screen Size). So in order to fix that I did this:
Code:
if selectedIndex == 0 {
vc.view.frame.size.height = 480
}
if selectedIndex == 1 {
vc.view.frame.size.height = 448
}
if selectedIndex == 2 {
vc.view.frame.size.height = 715
}
if selectedIndex == 3 {
vc.view.frame.size.height = 480
}
This was placed inside the IBAction. Right after vc is defined.
I am attempting to use UIPanGestureRecognizer to translate and dismiss a UITableViewController. I want the gesture to trigger translation of the TableView only when it has reached the bottom of its scroll view and dismiss the TableView when it has been translated 1/3 the height of the screen. I've tried to add the GestureRecognizer when the TableView has reached the bottom of its scroll view, but the application ends up adding the gesture recognizer and disabling the freedom to scroll back up in the TableView.
I can supply code upon request, but I should be able to follow general solutions you may have.
Thanks in advance.
Few extra things to note:
I've added the gesture recognizer to the table view
I want to create a similar effect to a particular view in Facebook's iPhone application. In their app, whenever you select a photo from a photo album, it presents a TableView that allows you to translate and dismiss it whenever you reach the any of the edges in its scrollview.
Here's the code I currently have:
override func scrollViewDidScroll(scrollView: UIScrollView) {
// MARK: - Scrollview dismiss from bottom
let scrollViewHeight = scrollView.frame.height
let scrollContentSizeHeight = scrollView.contentSize.height
let scrollOffset = scrollView.contentOffset.y
// Detects if scroll view is at bottom of table view
if scrollOffset + scrollViewHeight >= scrollContentSizeHeight {
println("Reached bottom of table view")
self.panGesture = UIPanGestureRecognizer(target: self, action: "slideViewFromBottom:")
self.imageTableView.addGestureRecognizer(panGesture)
}
}
func slideViewFromBottom(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translationInView(imageTableView).y
let velocity = recognizer.velocityInView(self.imageTableView)
var centerOfImageTableView = self.imageTableView.center.y
switch recognizer.state {
case .Began:
// Touches begin
println("Touches began")
case .Changed:
// Limits view translation to only pan up
if velocity.y < 0.0 {
centerOfImageTableView = self.screenbounds.height/2 + translation
}
case .Ended:
// Determines length of translation to be animated
let moveToTop = screenbounds.height + translation
// Animates view depending on view location at end of gesture
if translation <= -UIScreen.mainScreen().bounds.height/2 {
UIView.animateWithDuration(0.2) {
self.imageTableView.transform = CGAffineTransformMakeTranslation(0.0, -moveToTop)
return
}
delay(0.3) {
self.presentingViewController?.dismissViewControllerAnimated(false, completion: nil)
}
}
else {
UIView.animateWithDuration(0.3) {
self.imageTableView.transform = CGAffineTransformMakeTranslation(0.0, -translation)
return
}
recognizer.enabled = false
}
default:
println("Default executed")
}
}