I am trying to utilise panGestureRecogniser to enable users of my first app to move programmatically created textfields. Having searched for the solution I only found outdated code, for IBOutlets. The problem is that I struggle with setting the position of newly created textfields to the position that the users drags to. Everything is working just fine, when I test my code on a textfield create in a storyboard, connected via an IBOutlet. My problem is how to make a connection between the location from the didPan function and any new label created by generateTextField func. Another thing is that I want mu users to be able to create many textfields and each should be draggable, so any solution should include that. I paste my code below:
override func viewDidLoad() {
super.viewDidLoad()
}
func generateTextField() {
let textPanel = UITextField(frame: CGRect(x: 50, y: 50, width: 100, height: 30))
textPanel.textAlignment = NSTextAlignment.center
textPanel.textColor = UIColor.blue
textPanel.borderStyle = UITextBorderStyle.line
self.view.addSubview(textPanel)
let gest = UIPanGestureRecognizer(target: self, action: #selector(didPan))
textPanel.isUserInteractionEnabled = true
textPanel.addGestureRecognizer(gest)
}
#objc func didPan(sender: UIPanGestureRecognizer){
let location = sender.location(in: view)
if sender.state == .began {
print("Gesture began")
} else if sender.state == .changed {
print("Gesture is changing")
} else if sender.state == .ended {
print("Gesture ended")
}
}
#IBAction func generateTextFieldPressed(_ sender: Any) {
generateTextField()
}
In didPan you can retrieve textField from recognizer like:
let textField = sender.view as TextField
And after that change textField's frame.
Related
I have developed a UIView from For loop and basically it is create 3 Views from loop. and I have to add touch gesture on every View to call a method but I am unable to get current selected UIView.tag when I tap on it. it is only showing the .tag of the last view. here is my code.
for i in 0 ... 2 {
let productView = UIView()
productView.tag = i
productView.isUserInteractionEnabled = true
let producttap = UITapGestureRecognizer(target: self, action: #selector(self.ProductTapped))
productView.addGestureRecognizer(producttap)
productView.frame = CGRect(x: xOffset, y: CGFloat(buttonPadding), width: 200, height: scView1.frame.size.height)
xOffset = xOffset + CGFloat(buttonPadding) + productView.frame.size.width
scView1.addSubview(productView)
productIndex = productView.tag
}
and here is the method that I am calling from every UIView touch.
#objc func ProductTapped() {
print("",productIndex)
}
Your code should be using delegate/callback closure, but if you want to keep using tag, try change it to:
#objc func ProductTapped(_ sender: UITapGestureRecognizer) {
if let view = sender.view {
print(view.tag)
}
}
and the gesture attach to let producttap = UITapGestureRecognizer(target: self, action: #selector(self.ProductTapped(_:)))
productIndex does nothing here since it got overwritten on the loop
productIndex currently has no relationship to the tap gestures that you attach your views. You do set productIndex in the the loop but that's irrelevant to your gesture.
Perhaps you want
let producttap = UITapGestureRecognizer(target: self, action: #selector(productTapped(_:))
and
#objc func productTapped(_ gesture: UITapGestureRecognizer) {
print("tag is",gesture.view.tag)
}
I have a tap gesture that runs this code and it works once but then stops updating the zoomScale.
#objc func sampleTapGestureTapped(_ recognizer: UITapGestureRecognizer) {
print("tapped")
if self.scrollView_Image.zoomScale > self.scrollView_Image.minimumZoomScale {
scrollView_Image.setZoomScale(1, animated: false)
} else {
scrollView_Image.setZoomScale(3, animated: false)
}
}
The function runs and the tapped print is logged out but the zoomScale doesn't seem to change.
Perhaps the problem is your hard coded numbers. Here is how I do it:
if sv.zoomScale < sv.maximumZoomScale {
sv.setZoomScale(sv.maximumZoomScale, animated:anim)
}
else {
sv.setZoomScale(sv.minimumZoomScale, animated:anim)
}
Notice there are no hard coded numbers here. It works for any scroll view.
I've tried this in a small View Controller and it is working fine. It is possible that an action you are taking in the selector is stopping the gesture from working. You should probably post the selector function code as well, and anything relevant to setting up the Recognizer and the Image View
class ViewController: UIViewController {
var tappableImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
tappableImageView = UIImageView(frame: CGRect(x: 40, y: 40, width: 100, height: 100))
tappableImageView.backgroundColor = .red
view.addSubview(tappableImageView)
tappableImageView.isUserInteractionEnabled = true
let t = UITapGestureRecognizer(target: self, action: #selector(imageViewDoubleTapped(_:)))
t.numberOfTapsRequired = 2
tappableImageView.addGestureRecognizer(t)
}
#objc func imageViewDoubleTapped(_ recognizer: UITapGestureRecognizer) {
print("Double Tapped")
}
}
I'm creating many UI elements programmatically. Let's say 50 UILabels.
What is a proper way to access to labels properties?
Now i'm adding a tag to each label and next search label in [subview] array and get label properties through a 'sender':
func buttonTapped(sender: UIButton) {
for subview in containerView.subviews {
if let label = subview as? UILabel, label.tag == sender.tag {
// do stuff
}
}
}
Not sure it's most elegant way because there are some problems if we change labels to buttons.
Lets say:
func createButton() {
let button = UIButton(frame: CGRect(origin: ...,
size: ...))
button.addTarget(self, action: #selector(buttonTapped(sender:)), for: .touchUpInside)
let longTap = UILongPressGestureRecognizer(target: self, action: #selector(disableButton(sender:)))
longTap.minimumPressDuration = 1
button.addGestureRecognizer(longTap)
.......
containerView.addSubview(button)
}
And now i can't access to properties via 'sender' in disableButton(sender:) method, because sender is UILongPressGestureRecognizer.
Seems like im doing something wrong if it works for labels but not for buttons.
Please, guide me in right direction
First of all, I would keep and array with a reference to all of the labels so you don't have to iterate through all of the subviews to find them and keep this in order with the highest z value first. But if you don't want to do that here is the code that can find you view in one line; its not the most efficient thing in the world but for your number fo views it should be fine.
class ViewController: UIViewController {
let first = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
let second = UILabel(frame: CGRect(x: 25, y: 25, width: 100, height: 50))
override func viewDidLoad() {
first.text = "first"
first.backgroundColor = .blue
second.text = "second"
second.backgroundColor = .red
view.addSubview(first)
view.addSubview(second)
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
view.addGestureRecognizer(tap)
}
#objc func handleTap(sender: UITapGestureRecognizer) {
if let label = view.subviews.flatMap({$0 as? UILabel}).last(where: { $0.bounds.contains(sender.location(in: $0))}) {
print("You pressed: \(label)")
}
}
}
Note whats going on here:
We use flatMap to discard any subviews that are not UIlabels.
We use last to find the last item in the array (
because the top most views are last according to UIView.subviews) where the touch is inside that view's bounds.
Simple add this code when your creating your label
yourLbl.isUserInteractionEnabled = true
And use touchesBegan method to get your clicked label
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
if let lblView = touch?.view
{
if lblView.isKind(of: UILabel.self)
{
let yourLbl = lblView as! UILabel
//Access your clicked lable
}
}
}
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?
I have a view that has panGesture functionality, but I need to send a pan-gesture from one point to another programatically. Is there a way to do this in swift using an animation with a specific time interval? Here is my attempt at calling the pan gesture programmatically:
var upPanPoint = CGPoint(x: contentView.center.x, y: contentView.center.y + 500)
var upPan = panGestureRecognizer.setTranslation(upPanPoint, inView: self)
onSwipe(upPan)
here is the code that recognizes the pan gesture:
func onSwipe(panGestureRecognizer : UIPanGestureRecognizer!) {
let view = panGestureRecognizer.view!
print(view)
switch (panGestureRecognizer.state) {
case UIGestureRecognizerState.Began:
if (panGestureRecognizer.locationInView(view).y < view.center.y) {
self.viewState.rotationDirection = .RotationAwayFromCenter
} else {
self.viewState.rotationDirection = .RotationTowardsCenter
}
case UIGestureRecognizerState.Ended:
self.finalizePosition()
default:
let translation : CGPoint = panGestureRecognizer.translationInView(view)
view.center = self.viewState.originalCenter + translation
self.rotateForTranslation(translation, withRotationDirection: self.viewState.rotationDirection)
self.executeOnPanForTranslation(translation)
}
}
// The Pan Gesture
func createPanGestureRecognizer(targetView: UIImageView) {
var panGesture = UIPanGestureRecognizer(target: self, action:("handlePanGesture:"))
targetView.addGestureRecognizer(panGesture)
}
func handlePanGesture(panGesture: UIPanGestureRecognizer) {
// get translation
var translation = panGesture.translationInView(view)
panGesture.setTranslation(CGPointZero, inView: view)
println(translation)
// create a new Label and give it the parameters of the old one
var label = panGesture.view as UIImageView
label.center = CGPoint(x: label.center.x+translation.x, y: label.center.y+translation.y)
label.multipleTouchEnabled = true
label.userInteractionEnabled = true
if panGesture.state == UIGestureRecognizer.State.began {
// add something you want to happen when the Label Panning has started
}
if panGesture.state == UIGestureRecognizer.State.ended {
// add something you want to happen when the Label Panning has ended
}
if panGesture.state == UIGestureRecognizer.State.changed {
// add something you want to happen when the Label Panning has been change ( during the moving/panning )
} else {
// or something when its not moving
}
}
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture))
self.imageView.addGestureRecognizer(panGesture)
#objc func panGesture(sender: UIPanGestureRecognizer){
let point = sender.location(in: view)
let panGesture = sender.view
panGesture?.center = point
print(point)
}
With Swift version 4.2 you can set pan gesture programmatically using below code:
let panGesture = UIPanGestureRecognizer(target: self, action:(#selector(self.handleGesture(_:))))
self.view.addGestureRecognizer(panGesture)
#objc func handleGesture(_ sender: UIPanGestureRecognizer) {
switch sender.state {
case .began:
case .changed:
case .cancelled:
case .ended:
default:
break
}
}