UIGestureRecognizer is delayed after UIView has changed its size - swift

I have a view and a subview inside it.
In the subview there are a few Gesture Recognizers that work.
They are all added normally when the subview is created, for example:
let assetViewTapGesture = UITapGestureRecognizer(target: self, action: #selector(self.handleAssetViewTap(_:)))
assetViewTapGesture.delegate = self
assetView.addGestureRecognizer(assetViewTapGesture)
in the superview I have a UIPinchGestureRecognizer, that calls a function in the subview, which, in its turn changes sizes of frames in the subview:
public func scale(newWidth:CGFloat){
let factor:CGFloat = newWidth / self.internalAssetView.frame.width
self.internalAssetView.frame = CGRect(x: self.internalAssetView.frame.origin.x, y: self.internalAssetView.frame.origin.y, width: newWidth, height: self.internalAssetView.frame.height)
self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: newWidth + additionParameter, height: self.frame.height)
}
When I pinch, everything behaves well, and the views change their sizes the way they should.
The problem:
after the pinch ends, I tap in order to fire the TapGestureRecognizer inside the subview. It is being fired, but only after a few seconds. Actually - the more I pinch the views, the longer I wait, between tapping the screen and the actual firing of the recognizer. How come changing views's frames can affect the delay of a Gesture Recognizer's firing?

I do not know the reason for this however, I can offer a solution that might mitigate your issue.
There is no need to add an independent tap gesture, in-fact every single UIVIEW has a touches method. So you can delete the tap gesture and try typing this function
override func touchesBegan()
I've omitted the arguments, when you start typing touches... it will auto complete. Put the code you want to execute within it. Similarly there are touches started, touches ended methods also which you can use. Try the UIVIEW Touch methods. Hopefully they wont lag and your issue will be resolved.

Related

Why is touchesCancelled being called after UITapGestureRecognizer?

After finally finishing my coding within touchesBegan, touchesMoved, touchesEnded, touchesCancelled I started adding code for recognizing taps. I implemented it with the code below.
For some reason after every UITapGestureRecognizer, I get a touchesCancelled. Did I leave something out, is there a threshold I can modify, or do I somehow have to create my own code that says, I just did a tap, ignore the first cancel that comes along?
So essentially I get a touchesBegan, UITapGestureRecognizer, followed by a touchesCancelled.
class GameScene: SKScene, SKPhysicsContactDelegate{
let tapRec = UITapGestureRecognizer()
....
tapRec.addTarget(self, action:#selector(GameScene.tappedView(_:) ))
tapRec.numberOfTouchesRequired = 1
tapRec.numberOfTapsRequired = 1
self.view!.addGestureRecognizer(tapRec)
....
}
#objc func tappedView(_ sender:UITapGestureRecognizer) {
....
}
That's just what a gesture recognizer is / does. When a gesture recognizer recognizes its gesture, the touches for this gesture recognizer are sent to their hit-test views as a touchesCancelled(_:with:) message, and then no longer arrive at those views. The gesture recognizer takes over so it signs the view off from its touch sequence in good order.
If you're going to have both a gesture recognizer and manual touch events, you have to do more work to mediate between them. If you really don't want the standard behavior, you can set the gesture recognizer's cancelsTouchesInView property to false.

UIView transition problem from bottom to up

In my application I want to animate a view from bottom to up in override func viewDidLayoutSubviews(). So far I have done this way to transit the view from bottom but in different device it is showing different UI. UIView transition is working but view is not fully transitioning from bottom expect iPhone XS. So far I have done these two ways :
First Step:
let currentFrame = self.animationVIew.frame
self.animationVIew.frame = CGRect(x: 0, y: self.view.frame.height, width: currentFrame.size.width, height: currentFrame.size.height)
UIView.animate(withDuration: 1.0) {
self.animationVIew.frame = currentFrame
self.loadViewIfNeeded()
}
Second Step:
UIView.transition(with: animationVIew,
duration: 0.5,
options: [.transitionFlipFromBottom],
animations: {
},
completion: nil)
But for both way I am facing same problem. No luck!!
It was my mistake I did not think in proper way to solve it.Anyway if anyone face similar kind of problem then may be this answer will help them. I have just changed the current autolayout to fix this issue.This problem was happening because of constraint between animationView and topView. The distance constraint between these two view was fixed, That is why when animation was happening it was resizing the animationView to adjust the constraints.So my solution was making the animation view height constraint fixed and have changed the priority required to low of distance constraint between animationView and topView in storyboard

UIButton action is not triggered after constraint layouts changed

I have got an UIButton on a storyboard ViewController. When I load data into the form and the layout is significantly changing the button does not recognise the touch action.
I have figured out that when button is visible on the scrollview right after it if filled with data, the touch action works.
If the data too long and the button is not visible at first, just when it is scrolled into the display, the touch action does not work.
I was checking if something is above the button, but nothing. I have tried to change the zPosition of the button, not solved the problem.
What can be the issue?
I have made custom classes from the UIScrollView and the UIButton to check how the touches event triggered. It is showing the same behaviour, which is obvious. If the button is visible right at the beginning, the UIButton's touchesBegan event is triggered. If the button moves down and not visible at the beginning, it is never triggered, but the scrollview's touchesBegan is called instead.
Depending on the size of the data I load into the page sometimes the button is visible at the beginning, but the form can be still scrolled a bit. In this case the button still work, so it seems that this behaviour is not depending on if the scrollview is scrolled before or not, just on the initial visibility of the button.
Is there any layout or display refresh function which should be called to set back the behaviour to the button?
The code portion which ensures that the contentview is resized for the scroll if the filled data requires bigger space.
func fillFormWithData() {
dispDescription.text = jSonData[0]["advdescription"]
dispLongDescription.text = jSonData[0]["advlongdesc"]
priceandcurrency.text = jSonData[0]["advprice"]! + " " + jSonData[0]["advpricecur"]!
validitydate.text = jSonData[0]["advdate"]!
contentview.layoutIfNeeded()
let contentRect = CGRect(x: 0, y: 0, width: scrollview.frame.width, height: uzenetbutton.frame.origin.y+uzenetbutton.frame.height+50)
contentview.frame.size.height = contentRect.size.height
scrollview.contentSize = contentview.bounds.size
}
Ok, so another update. I have coloured the contentview background to blue and the scrollview background to white. When I load the data and resize the layout constraints, the contentview is resizing as expected, however now the scrollview is going to the bottom. After I scroll the view it is resizing to the original size which fits the screen. Now the button is only recognised when I touch the are which is blue behind. With the white background it is not recognised anymore, so it seems that the scrollview is hiding the button.
Let me get this clear the button is added in storyboard and it is a spritekit project?? If you are using zPosition?? Why don’t u connect the UIButton via the assistant editor as an IBAction then the action is always tied to the button.
You can also do it differently
Create an SKLabelNode and put it on the screen where you want to have the button and then set a name to it as myButton
override func touchesBegan(_ touches: Set<UITouch>, with event:
UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self)
let tappedNodes = nodes(at: location)
for node in tappedNodes {
if node.name == "myButton" {
// call your action here
}
}
}
}
EDIT 1:
You could also try auto resizing your scrollView.content this works also if you are adding any views via the app or programmatically
private func resizeScrollView(){
print("RESIZING THE SCROLLVIEW from \(scrollView.contentSize)")
for view in scrollView.subviews {
contentRect = contentRect.union(view.frame)
}
scrollView.contentSize = CGSize(width: contentRect.size.width, height: contentRect.size.height + 150)
print("THE CONTENT SIZE AFTER RESIZING IS: \(scrollView.contentSize)")
}
EDIT 2: I think I found the issue with your project. You need to move the MessageButton(UzenetButton) above DispDescription label in the object inspector in that way it will always be above your message textView.
At the moment the UzeneButton is at the very far back in your view hierarchy so if your textView is resizing whilst editing it covers the button that is why you cannot click on it.
See #Endre Olah,
To make situation more clear do one more thing, set clipToBound property of contentview to true.
you will notice that after loading of data your button not fully visible, it means it is shifting out of bound of its parentView (ContentView)
And that's why button is not taking your touch. However, if you carefully touch upper part of button it still do its job. Because upper part is still in bound of ContentView
Solution :
After loading of data you have to make sure that you increase height of ContentView such that button should never go out of bound of its parentView(ContentView).
FOR EXAMPLE
#IBOutlet var heightConstraintOfContentView : NSLayoutConstraint!
After loading of data
let contentRect = CGRect(x: 0, y: 0, width: scrollview.frame.width, height: uzenetbutton.frame.origin.y+uzenetbutton.frame.height+50)
heightConstraintOfContentView.constant = contentRect.size.height
contentView.layoutIfNeeded()
I use following steps when I need to use scrollview with dynamic content:
1) Firstly add a scrollView with top, bottom, trailing and leading is 0 to super view.
2) Add a view to scrollView and view's trailing, leading bottom and top space to scrollView can be set to 0 (or you can add margin optionally).
3) Now, you should add UI elements like buttons, labels with appropriate top, bottom, trailing and leading margins to each other.
4) Lastly, add equal height and equal width constraint to view with Safe Area:
and change equal height priority of view to 250:
It should solve your problem with UIScrollView.
Finally, I have found the solution in another chain, once it became clear that the scrollview's contentview is resizing on scroll event to the original size. (Not clear why this is like this, but that is the fact.)
So I had to add a height constraint to the contentview in the storyboard and create an outlet to it and adjust this constraint when the content size is changing, like this:
#IBOutlet weak var ContentViewHeight: NSLayoutConstraint!
func fillFormWithData() {
dispDescription.text = jSonData[0]["advdescription"]
dispLongDescription.text = jSonData[0]["advlongdesc"]
priceandcurrency.text = jSonData[0]["advprice"]! + " " + jSonData[0]["advpricecur"]!
validitydate.text = jSonData[0]["advdate"]!
contentview.layoutIfNeeded()
let contentRect = CGRect(x: 0, y: 0, width: scrollview.frame.width, height: uzenetbutton.frame.origin.y+uzenetbutton.frame.height+50)
contentview.bounds = contentRect
scrollview.contentSize = contentRect.size
----------- This is the key line to the success ----------
ContentViewHeight.constant = contentRect.size.height
----------------------------------------------------------
}
After this is added, it works perfectly.

Incorrect Frame Position when defined in ViewDidLayoutSubviews

I have an imageView inside an ovalView constrained with auto layout.
Inside viewDidLayoutSubviews(), I am creating a UILabel
let label = UILabel(frame: imageView.frame)
imageView.superview?.addSubview(label)
The frame of label shows slightly off-centered from imageView.
However, if I move my above code inside viewDidAppear(), label is perfectly centered to imageView but user sees the change which is undesireable.
How do I keep my code in viewDidLayoutSubviews() and make it work?
When viewDidLayoutSubviews is called you can't assume that your views have their final frames before that method is called. In your case that's what happened. When you call viewDidAppear your view has drawn all its frames and you get the right frame that's why it works for you. So call it in viewDidAppear instead.
For others, who may be facing similar issues. I was able to keep my code in viewDidLayoutSubviews() by adding
self.view.layoutIfNeeded()
in my code after
let label = UILabel(frame: imageView.frame)
imageView.superview?.addSubview(label)

viewDidLayoutSubviews is called when label changes (xcode) (swift)

I have a label, toggle button and an animation in my code. When I press the toggle button -> animation starts , label changes. Below is my code sample.
override func viewDidLayoutSubviews() {
println("viewDidLayoutSubviews is called")
// Initial state of my animation.
}
#IBAction func Toggled(sender: AnyObject) {
CallTextChange()
// Two different Animations
}
func CallTextChange() { // Change text here}
Every time I change the text in label viewDidLayoutSubviews is called.
Is there a way to stop calling it every time I change the label?
I found the answer for my problem.
When we create UIImage by drag and dropping from the object provided by Xcode, the image is temporary placed where it was statically placed. So when animating in middle when viewDidLayoutSubviews is called the image is placed in the static place.
So the UIImage has to be created programmatically.
CreateImage.image = UIImage(named: "Image.png")
CreateImage = UIImageView(frame: CGRect(x: 0, y: 0, width: CreateImage.image!.size.width/6, height: CreateImage.image!.size.height/6))
self.view.addSubview(CreateImage)
Try to pass one boolean flag values in function
I think when you change text of and UILabel it will change its intrinsic content size that in turn will notify the system to relayout the view hierarchy. So it seems inevitable that viewDidLayoutSubviews will be called.
Either you put a boolean flag to make your initial animation code execute once only or move your code to other place like viewDidLoad() method.