I have a UIViewController with a separated UIView inside it. This view has some textfields. If the user taps on one of the fields, the view will automatically fit his center.y. In the end the textfield which the user tapped will be in the middle of the screen. But in my case the view doesn't move. In the simulator it worked great, but on a real phone nothing happens. Only the output center.y position changes. I fixed the UIView with constraints.
Here is the code for the tap action.
func textFieldDidBeginEditing(_ textField: UITextField) {
textField.layer.borderWidth = 0
textField.clearsOnBeginEditing = true
textField.text = ""
print("Began")
let textFieldCoInView = textField.center
let coInSelf = self.dataView.convert(textFieldCoInView, to: self.view)
print(coInSelf)
print(self.view.center.y)
if(coInSelf.y > self.view.center.y) {
dataViewOriginY = dataView.center.y
print("Works")
self.dataView.center.y = self.dataView.center.y - (coInSelf.y - self.view.center.y)
print(self.dataView.center.y)
}
}
"I fixed the UIView with constraints."
If the view you're trying to move has constraints on it, autolayout is probably fighting you. to test that theory try turning off constraints for the view you're moving.
If that's it, you'll probably need to do something like make outlets to the constraints and change the values in your code (or not use constraints).
In any case, I think you'll find that autolayout is fighting you and keeping the view where its constraints say it belongs.
Please check the constraint you have set for the UIView . and also check the console for errors. Constraints can stop the code to change view. Maybe turn off the constraint and check if the code works.
Related
I have a UIView that contains many UIView subviews. When the user tries to pan gesture one of the subviews, I want them to be able to drag it anywhere else on the screen.
However, I can only drag these smaller views within the bigger UIView they are contained in. When the user first tries to pan gesture the smaller view, is there any way to programmatically create a second UIView on top and then drag around the second UIView instead? The second UIView would be completely new from the smaller view that was first touched.
This is the handler function I have so far, just for reference. I don't know where I would programmatically create the second UIView, though. Any help would be greatly appreciated:
#objc func handlePanGesture(sender: UIPanGestureRecognizer) {
// get translation
var translation = sender.translation(in: view)
sender.setTranslation(CGPoint.zero, in: view)
// drag around current UIView
var newView = sender.view as! UIView
newView.center = CGPoint(x: newView.center.x+translation.x, y: newView.center.y+translation.y)
newView.isMultipleTouchEnabled = true
newView.isUserInteractionEnabled = true
if sender.state == UIGestureRecognizer.State.began {
// add something you want to happen when the Label Panning has started
}
if sender.state == UIGestureRecognizer.State.ended {
// add something you want to happen when the Label Panning has ended
}
if sender.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 it's not moving
}
}
Is there any way to be able to drag the smaller views anywhere while maintaining the same layout I have right now
Because of the way touch works, it is impossible by default for a view to be touched when it reaches the boundaries of its superview. But you can overcome that limitation by overriding hitTest in the superview.
A very common pattern that enables dragging of views outside of their superview, is to create a snapshot or clone of the original view as soon as the gesture begins, you can of course hide the original view so to the user it feels like the same UI element is being dragged. It's a pattern used by Apple in their drag and drop sample code here
https://developer.apple.com/documentation/uikit/drag_and_drop/adopting_drag_and_drop_in_a_custom_view
Have a look at line 36 in ViewController+Drag.swift for the relevant code.
I'm having a TopContainer View then a Scrollview and then again a BottomContainer view.
Now The constraints are that the scrollviews top is to the bottom of topContainer and that the bottom of the scrollview is to the top off the bottomcontainer.
When I start the viewcontroller and see the view hierarchy it is good.
But then when the keyboard shows I want to modify the autolayout constraint so that the bottomcontainer moves up.
So I thought I save the constraint and the change the constant like this:
private var toolbarBottomConstraint: NSLayoutConstraint?
self.toolbarBottomConstraint = self.toolbar.autoPinEdgeToSuperviewEdge(.Bottom, withInset: 0)
And then when the keyboard popups up I do this:
func keyboardWillShow(notification:NSNotification){
var userInfo = notification.userInfo!
var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue()
keyboardFrame = self.view.convertRect(keyboardFrame, fromView: nil)
self.toolbarBottomConstraint?.constant = keyboardFrame.size.height
}
This doesn't do anything. The keyboard height is exactly the offset the bottom needs to be on top of the keyboard.
Why is the view not changing? I always change constants like that and it always works. Do I need to do something special because it's now with the keyboard.
If you're building for iOS 9 or above, I wrote a simple library that takes care of automatically adjusting views when a keyboard appears. It's pretty flexible and it should work seamlessly.
If you do still want to roll this on your own, it looks like you haven't added a listener for the notification anywhere. You'll need to do that before messages are sent to the keyboardWillShow(_:) method.
Have you tried calling setNeedsLayout() after the set of the constraint?:
self.toolbarBottomConstraint?.constant = keyboardFrame.size.height
self.view.setNeedsLayout()
My app has two views, one in the top half of the screen, one in the bottom half of the screen. The top half view is called mainView, and the bottom half is called tableView. I have a UITextField in the mainView at the bottom of the mainView. So, I am getting the difference between the tableView's height and the keyboard so I can move the mainView up/down enough so that the keyboard will be directly below the uitextfield. The code I am using below works on iphone 4s and iphone 5, but anything bigger leaves a little bit of whitespace between the textfield and the keyboard. How can I fix this?
func keyboardShown(notification: NSNotification) {
let info = notification.userInfo!
let value: AnyObject = info[UIKeyboardFrameEndUserInfoKey]!
let rawFrame = value.CGRectValue
let keyboardFrameHeight = view.convertRect(rawFrame, fromView: nil).height
let bottomSpace = tableView.bounds.height
let spaceDifference = (keyboardFrameHeight - bottomSpace)
view.layoutIfNeeded()
self.mainViewProportionalHeight.constant -= spaceDifference
animLayout(0.5)
}
To reiterate:
mainView = top half of the view
tableView = bottom half of the view
There is a UITextField attached to the bottom of the mainView, so that the bottom of the mainview = bottom of the mainView. Basically, i am trying to align the mainView right above the keyboard without any whitespace in between.
The easiest way I've found to solve your problem is with the CocoaPod, IQKeyboardManager.
You just have to add it to your podfile and then run pod install and it takes care of positioning your keyboard right below whatever text field your writing in. (Even ones that the keyboard would normally cover)
Here's a link to it: https://cocoapods.org/?q=iqkeyboardmanager
I created an IBOutletCollection with some UIElements (Label, Button, TextField) that i would like to move on a special event.
thats what I have so far:
#IBAction func moveDown(sender: UIButton) {
UIView.animateWithDuration(0.4, animations: {
for subview in self.moveOutViews {
var offset = CGFloat(700);
var newY = subview.frame.origin.y + offset;
subview.frame = CGRectMake(subview.frame.origin.x, newY, subview.frame.size.width, subview.frame.size.height);
}
}, completion: {
(value: Bool) in
UIView.commitAnimations();
});
}
now my problem is, when the Animation is completed, and i want to interact with the UIElements (TextField) on the new position, all views 'jump' back to the origin position.
maby it's a problem that the 'sender' is part of the IBOutletCollection?
It's an iPad for iOS 8.3 app and i am using Swift.
Edit: I made a workaround with an UIScrollView. But I would like to know why the first idea isn't working.
Since we don't know how your view is setup I will have to guess that it is one of two things:
Like #Paulw11 suggested in his comment. You are editing the frames of views that are held by constraints. When the view is laid out again all views snap back to positions determined by the constraints. Event though you say that you are not using constraints it is possible that translatesAutoresizingMaskIntoConstraints is enabled for your views or that your xib/storyboard is set to use constraints and so constraints are automatically generated for you. One way to check this is to just log the constraints property of a subview.
You have code somewhere else in your app that is changing the position of your views and it is inadvertently being called.
How can I check if the keyboard is blocking my view?
I have UIButton that is in the centre of the screen. I also have a UITextfield that makes the keyboard appears. When I run the app on iPhone 4 the keyboard block the button, but on other models, it doesn't. I have a method that scrolls up the view when the keyboard appears. But I only want to scroll up in case the view is blocked. I can check the model of the iPhone and then decide if to scroll or not, but I thought checking if the Button is blocked would be better. How can I do it?
I solved this problem in an app like so:
Embed the UI for that screen in a UIScrollView, but configure it so that scrolling is not enabled (scrollEnabled property from code, checkbox if you're using a storyboard).
When the keyboard notification is received, get the frame from the button, then call scrollRectToVisible:animated: on the scroll view. It'll move the content the minimum amount necessary to make the button visible, which will be not at all if the screen is big enough.
Using the UIKeyboardWillShowNotification, you can get the height of the keyboard like this:
NSValue *keyboardRect = [notification.userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey];
CGFloat keyboardHeight = MIN(keyboardRect.CGRectValue.size.width, keyboardRect.CGRectValue.size.height);
The get the relevant "lowest" point of your button. (like buttonMaxY = CGRectGetMaxY(yourButton.frame)).
Use the scroll methods you have implemented, but scroll only if necessary: keyboardHeight+buttonMaxY > the height of the screen.
When keyboard is about to appear, a UIKeyboardWillShowNotification notification is posted with the frame of the keyboard. You can calculate and see if the text field frame intersects with the keyboard frame, and scroll.
See documentation on keyboard notifications here.
Following Leo Natan's explanation, you can try something like:
func keyboardWillShow(notification: Notification) {
guard let keyboardFrame = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? CGRect,
let viewFrame = myView.frame else {
log.error("I cannot calculate keyboard & view frame")
return
}
if keyboardFrame.intersects(viewFrame) {
// view covered by Keyboard
}
}
pay attention to use UIKeyboardFrameEndUserInfoKey, you can check this link for further details.