Hi i'm making an app where you can decorate a Christmas tree with baubles i have wrote code to be able to drag it around the view but i run out of baubles how do i make it so i can have lots of baubles to put on the christmas tree. I have tried duplicating one bauble lots of times but then added them all to the code and it doesn't work it drags them all at the same time. If you look in the picture it has the bauble in it but i want it to drag one but one to still be there to put on
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var red_bauble_1: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let gesture = UIPanGestureRecognizer(target: self, action: #selector(ViewController.wasDragged(_ :)))
red_bauble_1.addGestureRecognizer(gesture)
red_bauble_1.isUserInteractionEnabled = true
}
#objc func wasDragged (_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: self.view)
let red_bauble_1 = gesture.view
red_bauble_1?.center = CGPoint(x: red_bauble_1!.center.x + translation.x, y: (red_bauble_1?.center.y)! + translation.y)
gesture.setTranslation(CGPoint.zero, in: self.view)
}
}
If you are adding a variable number of ornaments as subviews and want to keep a reference to them, just have an array for them, e.g.
var ornaments = [UIView]() // or, if they're image views, `[UIImageView]()`
then you can add your ornaments to that array as you add them to your tree and you have an array to keep track of all of them.
For example:
class ViewController: UIViewController {
#IBOutlet weak var treeImageView: UIImageView!
#IBOutlet weak var ornamentToolImageView: UIImageView!
private var currentOrnament: UIView!
private var ornaments = [UIView]()
override func viewDidLoad() {
super.viewDidLoad()
let pan = UIPanGestureRecognizer(target: self, action: #selector(dragOrnamentFromToolbar(_:)))
ornamentToolImageView.addGestureRecognizer(pan)
}
/// Gesture for dragging ornament from toolbar on to the tree
///
/// This creates new ornament and adds it to view hierarchy as well as to our array of `ornaments`
#IBAction func dragOrnamentFromToolbar(_ gesture: UIPanGestureRecognizer) {
let location = gesture.location(in: treeImageView)
switch gesture.state {
case .began:
gesture.view?.isHidden = true // temporarily hide toolbar view so it feels like we're dragging it
let image = UIImage(named: "ornament")!
currentOrnament = UIImageView(image: image)
ornaments.append(currentOrnament)
treeImageView.addSubview(currentOrnament)
fallthrough
case .changed:
currentOrnament.center = location
case .ended, .cancelled:
// when done, add new pan gesture for dragging around new ornament
gesture.view?.isHidden = false // restore toolbar view
currentOrnament.isUserInteractionEnabled = true
let pan = UIPanGestureRecognizer(target: self, action: #selector(dragExistingOrnament(_:)))
currentOrnament.addGestureRecognizer(pan)
currentOrnament = nil
default:
break
}
}
/// Gesture for dragging existing ornament
///
/// This grabs existing and allows you to drag it around
#IBAction func dragExistingOrnament(_ gesture: UIPanGestureRecognizer) {
let location = gesture.location(in: treeImageView)
switch gesture.state {
case .began:
currentOrnament = gesture.view
fallthrough
case .changed:
currentOrnament.center = location
default:
break
}
}
}
That yields:
Related
Looking to add a tap gesture to an array of UIViews - without success. Tap seems not to be recognised at this stage.
In the code (extract) below:
Have a series of PlayingCardViews (each a UIView) showing on the main view.
Brought together as an array: cardView.
Need to be able to tap each PlayingCardView independently (and then to be able to identify which one was tapped).
#IBOutlet private var cardView: [PlayingCardView]!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(tapCard(sender: )))
for index in cardView.indices {
cardView[index].isUserInteractionEnabled = true
cardView[index].addGestureRecognizer(tap)
cardView[index].tag = index
}
}
#objc func tapCard (sender: UITapGestureRecognizer) {
if sender.state == .ended {
let cardNumber = sender.view.tag
print("View tapped !")
}
}
You need
#objc func tapCard (sender: UITapGestureRecognizer) {
let clickedView = cardView[sender.view!.tag]
print("View tapped !" , clickedView )
}
No need to check state here as the method with this gesture type is called only once , also every view should have a separate tap so create it inside the for - loop
for index in cardView.indices {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapCard(sender: )))
I will not recommend the selected answer. Because creating an array of tapGesture doesn't make sense to me in the loop. Better to add gesture within PlaycardView.
Instead, such layout should be designed using UICollectionView. If in case you need to custom layout and you wanted to use scrollView or even UIView, then the better approach is to create single Gesture Recognizer and add to the superview.
Using tap gesture, you can get the location of tap and then you can get the selectedView using that location.
Please refer to below example:
import UIKit
class PlayCardView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
backgroundColor = UIColor.red
}
}
class SingleTapGestureForMultiView: UIViewController {
var viewArray: [UIView]!
var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView = UIScrollView(frame: UIScreen.main.bounds)
view.addSubview(scrollView)
let tapGesture = UITapGestureRecognizer(target: self,
action: #selector(tapGetsure(_:)))
scrollView.addGestureRecognizer(tapGesture)
addSubviews()
}
func addSubviews() {
var subView: PlayCardView
let width = UIScreen.main.bounds.width;
let height = UIScreen.main.bounds.height;
let spacing: CGFloat = 8.0
let noOfViewsInARow = 3
let viewWidth = (width - (CGFloat(noOfViewsInARow+1) * spacing))/CGFloat(noOfViewsInARow)
let viewHeight = (height - (CGFloat(noOfViewsInARow+1) * spacing))/CGFloat(noOfViewsInARow)
var yCordinate = spacing
var xCordinate = spacing
for index in 0..<20 {
subView = PlayCardView(frame: CGRect(x: xCordinate, y: yCordinate, width: viewWidth, height: viewHeight))
subView.tag = index
xCordinate += viewWidth + spacing
if xCordinate > width {
xCordinate = spacing
yCordinate += viewHeight + spacing
}
scrollView.addSubview(subView)
}
scrollView.contentSize = CGSize(width: width, height: yCordinate)
}
#objc
func tapGetsure(_ gesture: UITapGestureRecognizer) {
let location = gesture.location(in: scrollView)
print("location = \(location)")
var locationInView = CGPoint.zero
let subViews = scrollView.subviews
for subView in subViews {
//check if it subclass of PlayCardView
locationInView = subView.convert(location, from: scrollView)
if subView.isKind(of: PlayCardView.self) {
if subView.point(inside: locationInView, with: nil) {
// this view contains that point
print("Subview at \(subView.tag) tapped");
break;
}
}
}
}
}
You can try to pass the view controller as parameter to the views so they can call a function on parent view controller from the view. To reduce memory you can use protocols. e.x
protocol testViewControllerDelegate: class {
func viewTapped(view: UIView)
}
class testClass: testViewControllerDelegate {
#IBOutlet private var cardView: [PlayingCardView]!
override func viewDidLoad() {
super.viewDidLoad()
for cardView in self.cardView {
cardView.fatherVC = self
}
}
func viewTapped(view: UIView) {
// the view that tapped is passed ass parameter
}
}
class PlayingCardView: UIView {
weak var fatherVC: testViewControllerDelegate?
override func awakeFromNib() {
super.awakeFromNib()
let gr = UITapGestureRecognizer(target: self, action: #selector(self.viewDidTap))
self.addGestureRecognizer(gr)
}
#objc func viewDidTap() {
fatherVC?.viewTapped(view: self)
}
}
I have made a draggable button
let buttonGesture = UIPanGestureRecognizer(target: self, action: #selector(draggedButton(_:)))
menuButton.isUserInteractionEnabled = true
menuButton.addGestureRecognizer(buttonGesture)
When the button drag to certain position, image will be changed.
#objc func draggedButton(_ sender:UIPanGestureRecognizer){
self.view.bringSubview(toFront: menuButton)
let translation = sender.translation(in: self.view)
menuButton.center = CGPoint(x: menuButton.center.x + translation.x, y: menuButton.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: self.view)
if(menuButton.center.x < 100){
menuButton.setImage(UIImage(named: "newimage"), for: UIControlState.normal)
}
}
But after setImage, button will reset position, how to prevent this?
Try setting button background image instead of button image. Since setting image will rescale its position.
I had same problem and heres my solution:
unset the image, make button blank with no graphics in it
create new UIImageView including your UIImages, for initial and second state
in viewDidLoad() in UIViewController set UIImageView.bounds = UIButton.bounds set the initial image
call UIButton.addSubview(UIImageView)
in your method where you are using UIButton.setImage() call UIImageView.image = UIImage
Heres an example:
class VC : UIViewController{
let image_initial : UIImage = UIImage("img_1")
let image_second : UIImage = UIImage("img_2")
let imageview_menu : UIImageView = UIImageView()
#IBOutlet weak var button_menu: UIButton!
override func viewDidLoad(){
super.viewDidLoad()
//...
self.imageview_menu.bounds = button_menu.bounds
self.imageview_menu.image = image_initial
self.button_menu.addSubview(imageview_menu)
//...
}
#objc func draggedButton(_ sender:UIPanGestureRecognizer){
//...your code
if(menuButton.center.x < 100){
imageview_menu.image = image_second
}
}
}
Hope this helps if you haven't found solution. :)
I've got a UICollectionView that typically contains 100-200 cells that scrolls both horizontally and vertically. I am trying to provide a simple UIView popup with 3 icons when the user performs a LongPress gesture to allow them to easily navigate to a couple specific cells within the UICollectionView.
I'd like the start of the LongPress to bring up the popup, then the user would drag their finger to one of the three icons and release to select that icon.
The problem I'm having is I can't figure out how to get either the view containing the 3 icons or the icons themselves to respond to the end of the LongPress gesture.
Here's a simplification of the UIViewController:
class MyViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var myCollectionView: UICollectionView!
#IBAction func longTouch(_ sender: UILongPressGestureRecognizer) {
if sender.state == .began {
self.menu!.frame = CGRect(x: sender.location(in: self.view).x - 20,
y: sender.location(in: self.view).y - 75,
width: 100, height: 100)
self.menu!.isHidden = false
}
else if sender.state == .ended {
self.menu!.isHidden = true
}
}
// Popup for long touch
var menu: UIView?
// MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
self.initMenu()
}
// MARK:- UICollectionViewDelegate
...
// MARK:- UICollectionViewDataSource
...
// MARK:- UIScrollViewDelegate
...
private func initMenu() -> Void {
self.menu = UIView()
self.menu!.isUserInteractionEnabled = true
self.menu!.frame = CGRect(x: 0,
y: 0,
width: 100,
height: 50)
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(menuLongPressHandler))
self.menu!.addGestureRecognizer(longPress)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
self.menu!.addGestureRecognizer(tapGesture)
// There would be ImageView icons here as well, which I've left out for simplicity
self.menu!.isHidden = true
}
func menuLongPressHandler() {
print("Long Press")
}
func handleTap(sender: UITapGestureRecognizer) {
print("Tap")
}
}
Neither the "Tap" or "Long Press" are printed when the LongPress ends while over the View. Any suggestions on how to get the view to capture the end of that LongPress gesture?
If you look into the image, you'll see a textview, button and a slider.
I have completed all the steps except for the last one which is, when I tap on the slider, it constantly disappear. I know it disappear because I implemented UITapGestureRecognizer.
I guess my question is, I want the slider to disappear everytime I tap anywhere on the screen but when I am using the slider, I dont want the slider to disappear which is happening now every time I release my tap.
I have tried implementing one more UITapGestureRecognizer in sizeRefont with a function to keep sizeRefont.isHidden false but when I do that, the slider will not disappear whenever I tap on the screen.
I tried putting sizeRefont.isHidden = false in sizeRefont action and it doesnt work either.
class ResizeController: UIViewController {
#IBOutlet weak var sizeRefont: UISlider!
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissRefontSize(_:)))
view.addGestureRecognizer(tap)
}
#IBAction func sizeRefont(_ sender: AnyObject) {
let fontSize = CGFloat(sizeRefont.value)
textView.font = UIFont(name: textView.font!.fontName, size: fontSize * 30.0)
}
#IBAction func showSlider(_ sender: Any) {
sizeRefont.isHidden = false
}
func dismissRefontSize(_ sender: UITapGestureRecognizer) {
if sender.location(in: sizeRefont){
sizeRefont.isHidden = false
} else {
sizeRefont.isHidden = true
}
}
}
There is an error on if sender.location(in: sizeRefont) where it says CGPoint is not convertible to Bool
Image
First thing you need to do is you need to Adjust the dismissRefontSize() method to to the following :
func dismissRefontSize(_ sender: UITapGestureRecognizer) {
let location = sender.location(in: view)
If sizeReFont.frame.contains(location) {
// do nothing
}else {
sizeReFont.isHidden = true
}
}
The other thing you need to adjust is creating the tap recognized in your viewDidLoad() to the following:
let tap = UITapGestureRecognizer(target: self, action : #selector(dismissRefontSize(_:)))
Right now I have a scrollView that takes up the entire view controller. The code below is able to move the scrollView around but I want to move the whole view controller around. How would I do that?
override func viewDidLoad() {
pan = UIPanGestureRecognizer(target: self, action: "handlePan:")
self.scrollview.addGestureRecognizer(pan)
}
func handlePan(recognizer:UIPanGestureRecognizer!) {
switch recognizer.state {
case .Changed:
handlePanChanged(recognizer); break
case .Ended:
handlePanTerminated(recognizer); break
case .Cancelled:
handlePanTerminated(recognizer); break
case .Failed:
handlePanTerminated(recognizer); break
default: break
}
}
func handlePanChanged(recognizer:UIPanGestureRecognizer!) {
if let view = recognizer.view {
var translation = recognizer.translationInView(self.view)
println("moving")
view.center = CGPointMake(view.center.x, view.center.y + translation.y);
recognizer.setTranslation(CGPointZero, inView: self.view)
}
}
I've tried different variations of "self.view.center ...." "UIApplication.sharedApplication.rootViewController.view.center.." etc.
I infer from your other question that you want to a gesture to dismiss this view controller. Rather than manipulating the view yourself in the gesture, I'd suggest you use custom transition with a UIPercentDrivenInteractiveTransition interaction controller, and have the gesture just manipulate the interaction controller. This achieves the same UX, but in a manner consistent with Apple's custom transitions paradigm.
The interesting question here is how do you want to delineate between the custom dismiss transition gesture and the scroll view gesture. What you want is some gesture that is constrained in some fashion. There are tons of options here:
If the scroll view is left-right only, have a custom pan gesture subclass that fails if you use it horizontally;
If the scroll view is up-down, too, then have a top "screen edge gesture recognizer" or add some visual element that is a "grab bar" to which you tie a pan gesture
But however you design this gesture to work, have the scroll view's gestures require that your own gesture fails before they trigger.
For example, if you wanted a screen edge gesture recognizer, that would look like:
class SecondViewController: UIViewController, UIViewControllerTransitioningDelegate {
#IBOutlet weak var scrollView: UIScrollView!
var interactionController: UIPercentDrivenInteractiveTransition?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
modalPresentationStyle = .Custom
transitioningDelegate = self
}
override func viewDidLoad() {
super.viewDidLoad()
// ...
let edge = UIScreenEdgePanGestureRecognizer(target: self, action: "handleScreenEdgeGesture:")
edge.edges = UIRectEdge.Top
view.addGestureRecognizer(edge)
for gesture in scrollView.gestureRecognizers! {
gesture.requireGestureRecognizerToFail(edge)
}
}
// because we're using top edge gesture, hide status bar
override func prefersStatusBarHidden() -> Bool {
return true
}
func handleScreenEdgeGesture(gesture: UIScreenEdgePanGestureRecognizer) {
switch gesture.state {
case .Began:
interactionController = UIPercentDrivenInteractiveTransition()
dismissViewControllerAnimated(true, completion: nil)
case .Changed:
let percent = gesture.translationInView(gesture.view).y / gesture.view!.frame.size.height
interactionController?.updateInteractiveTransition(percent)
case .Cancelled:
fallthrough
case .Ended:
if gesture.velocityInView(gesture.view).y < 0 || gesture.state == .Cancelled || (gesture.velocityInView(gesture.view).y == 0 && gesture.translationInView(gesture.view).y < view.frame.size.height / 2.0) {
interactionController?.cancelInteractiveTransition()
} else {
interactionController?.finishInteractiveTransition()
}
interactionController = nil
default: ()
}
}
#IBAction func didTapDismissButton(sender: UIButton) {
dismissViewControllerAnimated(true, completion: nil)
}
// MARK: UIViewControllerTransitioningDelegate
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return DismissAnimation()
}
func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return interactionController
}
}
class DismissAnimation: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.25
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let from = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let container = transitionContext.containerView()!
let height = container.bounds.size.height
UIView.animateWithDuration(transitionDuration(transitionContext), animations:
{
from.view.transform = CGAffineTransformMakeTranslation(0, height)
}, completion: { finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
}
)
}
}
Personally, I find the notion of having top and bottom screen edge gestures to be a bad UX, so I'd personally change this modal presentation to slide in from the right, and then swiping from left edge to the right feels logical, and doesn't interfere with the built in top pull down (for iOS notifications). Or if the scroll view only scrolls horizontally, then you can just have your own vertical pan gesture that fails if it's not a vertical pan.
Or, if the scroll view only scrolls left and right, you can add your own pan gesture that is only recognized when you pull down by (a) using UIGestureRecognizerDelegate to recognize downward pans only; and (b) again setting the scroll view gestures to only recognize gestures if our pull-down gesture fails:
override func viewDidLoad() {
super.viewDidLoad()
// ...
let pan = UIPanGestureRecognizer(target: self, action: "handlePan:")
pan.delegate = self
view.addGestureRecognizer(pan)
for gesture in scrollView.gestureRecognizers! {
gesture.requireGestureRecognizerToFail(pan)
}
}
func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if let gesture = gestureRecognizer as? UIPanGestureRecognizer {
let translation = gesture.translationInView(gesture.view)
let angle = atan2(translation.x, translation.y)
return abs(angle) < CGFloat(M_PI_4 / 2.0)
}
return true
}
func handlePan(gesture: UIPanGestureRecognizer) {
// the same as the `handleScreenEdgeGesture` above
}
Like I said, tons of options here. But you haven't shared enough of your design for us to advise you further on that.
But the above illustrates the basic idea, that you shouldn't be moving the view around yourself, but rather use custom transition with your own animators and your own interactive controller.
For more information, see WWDC 2013 Custom Transitions Using View Controllers (and also WWDC 2014 A Look Inside Presentation Controllers, if you want a little more information on the evolution of custom transitions).