Good Evening!
I am trying to zoom UIScrollView, more specific all sub-views placed inside.
As far I understand, I need to implement UIScrollViewDelegate method:
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return subView // <--- subview inside my scroll view
}
In my test projects zoom simply won't work until I do this. But, with this technique I can only zoom (1) single view. My scroll-view have dozens of sub-views which I want to be zoomed too. I can solve this by placing one UIView inside UIScrollView and then other sub-views inside this container view, but I will like to ask is there better solution?
I have used both automatic mechanism of pinch-to-zoom of scrollview and later played with my own pinch gesture recogniser:
override func viewDidLoad() {
super.viewDidLoad()
...
let pinchGesture = UIPinchGestureRecognizer(target: self, action: "pinchHandler:")
scrollView.addGestureRecognizer(pinchGesture)
.
.
.
func pinchHandler(recognizer: UIPinchGestureRecognizer)
{
if recognizer.state == UIGestureRecognizerState.Began
{
if recognizer.scale < 1.0 {
scrollView.setZoomScale(1, animated: true)
} else {
scrollView.setZoomScale(2, animated: true)
}
}
}
In both cases, only 1 view is zoomed (one passed as result of viewForZoomingInScrollView function)
Thank you.
Related
I have a UIView that fill the entire screen, then I'm adding multiple small circle UIView within that container view, I want those small circle's UIView to be draggable using UIPanGestureRecognizer. But sometimes they happen to be on top of each other making the top UIView not clickable at all, it always select the bottom ones.
In the container UIView I implemented hitTest to be able to select only those child views.
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for planet in self.myPlanets.values {
if let presentation = planet.layer.presentation(), presentation.frame.contains(point) {
return planet
}
}
return nil
}
How can I make the top view receive the click even instead of the bottom view?
I handled draggable views by creating a UIView subclass, adding a UIPanGestureRecognizer and updating based on its inputs.
Using this method, whichever view is on top will receive the touch and you don't have to override hitTest on the superview.
I also added a delegate to update constraints if the view is constrained to the superview. By setting the delegate the UIView or ViewController (whichever is delegate) can update the constraints for the views you want to move.
Here's a simple implementation:
// Delegate protocol for managing constraint updates if needed
protocol DraggableDelegate: class {
// tells the delegate to move
func moveByTranslation(_ change: CGPoint)
}
class DraggableView: UIView {
var dragDelegate: DraggableDelegate?
init() {
// frame is set later if needed by creator
super.init(frame: CGRect.zero)
configureGestureRecognizers()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// setup UIPanGestureRecognizer
internal func configureGestureRecognizers() {
let panGR = UIPanGestureRecognizer.init(target: self, action: #selector(didPan(_:)))
addGestureRecognizer(panGR)
}
#objc func didPan(_ panGR: UIPanGestureRecognizer) {
// get the translation
let translation = panGR.translation(in: self).applying(transform)
if let delegate = dragDelegate {
// tell delegate to move
delegate.moveByTranslation(translation)
} else {
// move self
self.center.x += translation.x
self.center.y += translation.y
}
// reset translation
panGR.setTranslation(CGPoint.zero, in: self)
}
}
Here's how I implemented the delegate callback in the view controller using this view, since my view used constraints:
/// Moves the tool tray when dragging is triggered via the pan gesture recognizer in the tool tray (an instance of DraggableView).
///
/// - Parameter change: The x/y change provided by the pan gesture recognizer.
func moveByTranslation(_ change: CGPoint) {
// only interested in the y axis movements for this example
// update the constraint that moves this view
let newPos = constraint_tooltray_yAxis.constant + change.y
// this function limited the movement of the view to keep it within bounds
updateToolTrayPosition(newPos)
}
I want to be able to move an instance of UIImageView from a UIScrollView to a UIView that is outside the containing UIScrollView.
I've got the panGesture working but a UIImageView shows only inside the containing UIScrollView when being dragged and gets hidden if going outside the containing UIScrollView as shown in the screenshot image.
I've tried something like someScrollView.sendSubview(toBack: self.view) to set the layer order and also imageView.layer.zIndex = .. but it doesn't seem to work in my case.
How do I achieve something as shown in the screenshot image so it can be dragged to a target UIView outside its containing view?
And also if possible, how can I create a new instance of UIImageView as the panGesture begins so the original images stay.
#IBOutlet weak var someScrollView: UIScrollView!
var letters: [String] = ["g","n","d"]
override func viewDidLoad() {
super.viewDidLoad()
someScrollView.addSubview(createLetters(letters))
someScrollView.sendSubview(toBack: self.view)
}
func createLetters(_ named: String]) -> [UIImageView] {
return named.map { name in
let letterImage = UIImageView()
letterImage.image = UIImage(named: "\(name)")
addPanGesture(image: letterImage)
return letterImage
}
}
func addPanGesture(image: UIImageView) {
let pan = UIPanGestureRecognizer(target: self, action: #selector(ViewController.handlePan(sender:)))
image.addGestureRecognizer(pan)
}
#objc func handlePan(sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: view)
if let imageView = sender.view {
imageView.center = CGPoint(x:imageView.center.x + translation.x,
y:imageView.center.y + translation.y)
}
sender.setTranslation(CGPoint.zero, in: self.view)
switch sender.state {
case .began:
...
}
}
First off, it's good practice to prioritize gesture recognizers - tell the scroll view's pan gesture that it won't receive touches on account of your pan gestures (Yours comes first).
someScrollView.panGestureRecognizer.require(toFail: yourGestureRecognizer)
Unclip scroll-view's subviews to it's bounds - It's subviews will be visible when dragged outside the scrollView's bounds.
scrollView.clipsToBounds = false
You can convert the frame of your dragged view to the scrollView and newParentView ancestor's coord' system, like this (assuming self.view is scrollView & newParentView's ancestor).
let pannedViewFrame = someScrollView.convert(pannedView, to: self.view)
Then, in your gesture recognizer's selector, you can test frame intersection of pannedViewFrame and newParentView.frame, like this:
// You have an frame intersection
if pannedViewFrame.intersects(newParentView.frame) {
}
Now, if you test intersection of frames when your gesture recognizer's state is .cancelled, .ended or .failed, then:
The panning has ended AND pannedView is within newParentView's bounds
Last step, just convert pannedViewFrame to newParentView.frame's coord' using the same trick and added pannedView as subview to newParentView.
Another solution is to remove the pannedView from scrollView when GR state is .began and add it to scrollView and newParentView's common ancestor. The rest is the same as i previously mentioned.
Relatively new to iOS apps, and swift, so I'm a little lost on how to proceed.
Basically, I have a UIImageView, and i'm able to pinch to zoom/scroll, as my UIImageView is in a UIScrollView, but I'd like to add a double tap on the UIImage to zoom in as well.
I'm able to zoom in (just set the zoomScale to 3.0 for now), but I'd like to have a smooth transition. I did see the zoom(to:, animated:) method, but I wasn't quite sure on how to zoom into the center of the view.
Anyways, here's what I have thus far.
I've created the tapGestureRecognizer, and assigned it to the UIImageView
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapImageViewGesture))
self.Image1.isUserInteractionEnabled = true
self.Image1.addGestureRecognizer(tapGesture)
and below is the function to perform the zoom
func tapImageViewGesture(sender: UITapGestureRecognizer) {
print("Touched")
self.pinchZoomScroll1.zoomScale = 3.0
}
Is there a simple way I can have some sort of a smooth animation of zooming in?, or should I go with using the zoom function instead, as it already supports animation?
Thank you!!
Either you can set set animation true by using this :
self.pinchZoomScroll1.setZoomScale(3.0, animated: true)
or you can also use scrollView delegate func by setting scrollViewminimumScale and max scale:
pinchZoomScroll1.minimumZoomScale = 1.0
pinchZoomScroll1.maximumZoomScale = 6.0
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return Image1
}
I am trying to use Xcode IB to create a scrollable group of 16 stackviews positioned one above the other with a 17th stationary stackview at the top that does not scroll. I want to be able to interact with the scrollable stackviews, so I don't want them to bounce back up immediately after I scroll down.
Any programming required I would do in Swift.
Currently I have:
a vertical stackview at the top of the view (Overview Stack View) that contains
one horizontal stackview at the top of the Overview Stack View as the stationary element (this horizontal stackview contains 2 text fields)
a scrollview below that, which contains a UIView that in turn contains 16 horizontal stackviews positioned 50 units apart on the Y axis
I find that if I configure the scrollview with Bounces and Bounces Vertically in the Attributes Inspector, I can scroll the stackviews but they always bounce back up immediately, making them difficult or impossible to interact with. If I don't include Bounces and Bounces Vertically, the group of stackviews doesn't scroll at all.
Github repo here
This image shows the project in XCode:
I have read a number of questions and answers on Stackoverflow (which is how I got this far) but none of the proposed solutions has helped me solve this.
Any help will be greatly appreciated!
This works for my situation:
Make ViewController a UIScrollView delegate.
Create an outlet to the Main ScrollView.
Add code based on this answer. (I made some changes.)
See code for ViewController below (also in github).
Hope this helps someone!
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var mainScroll: UIScrollView!
let stackHeight: CGFloat = 30.0
override func viewDidLoad() {
super.viewDidLoad()
mainScroll.delegate = self
print("----- viewDidLoad -----")
let swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(ViewController.swiped(_:)))
swipeDown.direction = UISwipeGestureRecognizerDirection.Down
self.view.addGestureRecognizer(swipeDown)
let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(ViewController.swiped(_:)))
swipeUp.direction = UISwipeGestureRecognizerDirection.Up
self.view.addGestureRecognizer(swipeUp)
}
func swiped(gesture: UIGestureRecognizer)
{
if let swipeGesture = gesture as? UISwipeGestureRecognizer
{
switch swipeGesture.direction
{
case UISwipeGestureRecognizerDirection.Down:
print("DOWN")
if (mainScroll.contentOffset.y >= stackHeight) {
mainScroll.contentOffset.y = mainScroll.contentOffset.y - stackHeight
}
case UISwipeGestureRecognizerDirection.Up:
print("UP \(mainScroll.contentOffset.y)")
if (mainScroll.contentOffset.y <= stackHeight*16 ) {
mainScroll.contentOffset.y = mainScroll.contentOffset.y+stackHeight
}
default:
break
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Is there a way to determine the panning location of a UIPageViewController while sliding left/right? I have been trying to accomplish this but its not working. I have a UIPageViewController added as a subview and i can slide it horizontally left/right to switch between pages however i need to determine the x,y coordinates of where I am panning on the screen.
I figured out how to do this. Basically a UIPageViewController uses UIScrollViews as its subviews. I created a loop and set all the subviews that are UIScrollViews and assigned their delegates to my ViewController.
/**
* Set the UIScrollViews that are part of the UIPageViewController to delegate to this class,
* that way we can know when the user is panning left/right
*/
-(void)initializeScrollViewDelegates
{
UIScrollView *pageScrollView;
for (UIView* view in self.pageViewController.view.subviews){
if([view isKindOfClass:[UIScrollView class]])
{
pageScrollView = (UIScrollView *)view;
pageScrollView.delegate = self;
}
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSLog(#"Im scrolling, yay!");
}
My personal preference is not to rely too much on the internal structure of the PageViewController because it can be changed later which will break your code, unbeknownst to you.
My solution is to use a pan gesture recogniser. Inside viewDidLoad, add the following:
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handler))
gestureRecognizer.delegate = yourDelegate
view.addGestureRecognizer(gestureRecognizer)
Inside your yourDelegate's definition, you should implement the following method to allow your gesture recogniser to process the touches
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Now, you should be able to access the X/Y location of the user's touches:
func handler(_ sender: UIPanGestureRecognizer) {
let totalTranslation = sender.translation(in: view)
//...
}