iOS custom UIView as input keyboard [duplicate] - ios

This question already has answers here:
How to create a custom keyboard
(5 answers)
Closed 6 years ago.
I have huge issues when trying to use a custom view as a input keyboard for a text field.
The setup is pretty simple (for testing) : I have a UIView with one button, half the size of the screen and a text field somewhere above it.
I set up a dummy view to be the input view of the text field like so let dummyView : UIView = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
Also I added the following setting on viewDidAppear testView.frame.origin.y = view.bounds.height. This is of course ignored as the view appears on screen (putting it on viewDidLoad leads to same behaviour). However, putting it in viewdidLayoutSubviews leads to a very strange behaviour, the view does not appear on screen, but trying to animate to, let's say a 380 origin.y does nothing.
The best part is yet to come: if I hide the view, then the slide up animation (code bellow)
UIView.animateWithDuration(0.6, animations: {
self.testView.frame = self.whereToBe!
}, completion: {
finished in
if finished {
}
})
works just fine. However, trying to slide it down again and resigningFirstResponder on the text field on button press (code bellow)
UIView.animateWithDuration(0.6, animations: {
//self.testView.frame = self.whereToBe!
self.testView.frame.origin.y = self.view.frame.height
}, completion: {
finished in
if finished {
self.view.endEditing(true)
//self.inputField.resignFirstResponder()
self.testView.hidden = true
}
})
shows a fascinating wrong behaviour: the view slides from top to its current position. However, on second press on the button, it works just fine, it slides down BUT it reappears on screen after the animation has completed.
Can you please help me?

Use the view as the inputView of the textfield by simply setting
self.textField.inputView = self.pickerView;
and avoid all these custom animations.

Related

View/ViewController hierarchy and positioning issues

I have a problem which I try to solve with 2 approaches. Both work to some extent, but then fail. The help with any of them will be highly appreciated. Also any other working approaches are welcome.
This is the task that needs to be accomplished:
I have an input component. For simplicity, let's say it contains only text field and send button. It is a view controller. The send button is another view controller that is embedded as a child view controller. The input component is then later embedded as a child view controller itself to some higher level container.
This can't be changed and we have to take this as granted.
The input controller should be clever enough to be able to display itself above the keyboard whenever its text field becomes first responder.
So, the first approach is adding the input's component contentView as the text field's inputAccessoryView.
func textFieldDidBeginEditing(_ textField: UITextField) {
// do some staff
// This needs to be done, because in above-keyboard position the input component has another layout compared to when it's inactive
contentView.layoutIfNeeded()
// This is the actual adding itself to be displayed above the keyboard
inputField.inputAccessoryView = contentView
}
As far as I understand more staff happens under the hood including adding and removing child view controllers.
Then, when the user taps return button or the send button, the following code is executed:
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
which triggers the
func textFieldDidEndEditing(_ textField: UITextField) {
// We add contentView back where it was before and reset constraints
view.addSubview(contentView)
contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
Hacky as hell, this code works. The only problem is that the view controller hierarchy seems to be corrupted (the input component is no longer displayed in view debugger, but the text field and button are on screen, so I was ok with that).
Then I've found the corner case. When the user dismisses keyboard by tapping anywhere on screen, or when he/she activates another text field, the textFieldShouldReturn is not called, but the textFieldDidEndEditing is called.
As a result, the contentView is removed from the inputAccessoryView, but it doesn't appear where it was before.
Debugging revealed that its superview ends up being nil, which leads me to the conclusion, that iOS at some point removes it even though I've added it as a view's subview.
If I add contentView.removeFromSuperview() before view.addSubview(contentView), then the app crushes with this error message
Thread 1: Exception: "child view
controller:UICompatibilityInputViewController: 0x7fa13b2a3c00 should
have parent view controller:(null) but actual parent
is:UIInputWindowController: 0x7fa136045000"
I don't understand how my code is guilty of this, so probably I do something that I am not supposed to from Apple's perspective.
This is why I tried the second approach.
This time I don't do anything with the inputAccessoryView. Instead I just modify the bottom constraint of the contentView to move up when keyboard is displayed:
func textFieldDidBeginEditing(_ textField: UITextField) {
contentViewBottomConstraint.constant = // whatever the keyboard's height is
UIView.animate(withDuration: viewModel.animationDurationForInputField,
delay: 0,
options: .curveEaseOut, animations: { [weak self] in
self?.view.layoutIfNeeded()
})
}
and to hide
func textFieldDidEndEditing(_ textField: UITextField) {
contentViewBottomConstraint.constant = 0
UIView.animate(withDuration: viewModel.animationDurationForInputField,
delay: 0,
options: .curveEaseOut, animations: { [weak self] in
self?.view.layoutIfNeeded()
})
}
This works like a charm and actually this should be the correct approach without messing around with inputAccessoryView and child-parent relationships of the view controllers.
But, the problems begin when this input component is embedded into the detail part of the master/detail controller. On iPad, the detail part takes around half of the screen. So is the width of our input component. And when the keyboard is displayed, the input component is pushed up, but it's width remains the same - half of the screen.
To make it full screen, I get the reference to the top window, deactivate old leading and trailing constraints and add new one - between top window and contentView.
guard let keyWindow = UIApplication.shared.keyWindow else { return }
contentViewBottomConstraint.constant = viewModel.bottomOffsetForInputField
NSLayoutConstraint.deactivate(originalContentViewConstraints)
contentViewToKeyboardLeadingConstraint = contentView.leadingAnchor.constraint(equalTo: keyWindow.leadingAnchor)
contentViewToKeyboardLeadingConstraint?.isActive = true
contentViewToKeyboardTrailingConstraint = contentView.trailingAnchor.constraint(equalTo: keyWindow.trailingAnchor)
contentViewToKeyboardTrailingConstraint?.isActive = true
And it does become full screen, only half of it is not visible, because it is in the detail part of the master/detail, so it is overlapped by the master part.
And I am not able to bring it to front.
I tried adding it as subview to the window - crash because child/parent relationships are broken.
Moving subview to front - no effect (obviously, but I had to try).
Changing zPosition - no effect.
So, at the moment I've run out of ideas.
This is a very corner case, but I need to make it work because it (master/detail) is the actual layout in the application.
Thank you in advance for any help.

Swift iOS 9 keyboard appear does not shift view up, but works in iOS 10

Here's my code to shift the view up when a keyboard appears (also note that this view has a CollectionView):
if isKeyboardShowing {
UIView.animate(withDuration: animationDuration, animations: {
if self.isCollectionViewBlockingInput() {
self.view.frame.origin.y = -distanceShifted
// Keyboard is shifted less because view already shifts by distance
self.bottomConstraintForInput.constant = -keyboardViewEndFrame.height + distanceShifted
} else {
self.bottomConstraintForInput.constant = -keyboardViewEndFrame.height
}
})
}
Essentially the view's origin y is shifted up by a certain distance. This works for iOS 10, but in iOS 9 **when the view first loads and I open the keyboard for the first time, the view tries to shift up but fails. Instead, a black background appears to be shifted down momentarily, then disappears:
This only happens on opening the keyboard before scrolling the collection view. Once I start scrolling the collection view, the error goes away and the keyboard shifts up correctly again.
Also this error does not show up in iOS 10 at all.
Try this
UIView.animate(withDuration: 1.0, animations: {
//Do your constraint change part here
}, completion: {bool in
// Call here the layoutIfNeeded method
self.view.layoutIfNeeded()
})

How to compare if the coordinates are equal to the original coordinates of an object

Can someone please help me??
Lets start with the main information: I created an app that has a 2 view and one is called InfoView. This InfoView is located off screen with the following code:
(Code Hide View)
UIView.animateWithDuration(1.0, delay: 0.3, options: [ ], animations: {self.InfoView.center.x -= self.view.bounds.width + 150}, completion: nil)
Its automatically moved when the app starts. I placed the Code Hide View on ViewDidAppear.
When the button Info is pressed the InfoView ease in from left to right to its original coordinates, (As I placed it on the storyboard with x=12 and y=20 and the Views size depends on the screen size). I use the following code to bring it back.
(Code Show View)
UIView.animateWithDuration(1.0, delay: 0.0, options: [ ], animations: {self.InfoView.center.x += self.view.bounds.width + 150}, completion: nil)
The InfoView has a button that when pressed hides the view (Code Hide View) once again so that its off screen.
My problem is that when I tryout the app if the Iphone receives a call (phone call or sms or etc), after the call had ended it would show the InfoView on screen and without pressing the Show View Code to run.
Ive then hidden the view with:
InfoView.Hidden = True
And when I run the Show View code it changes to false and when it hides it turns to true and so on. However now when I receive a call and after the call has ended One can't see the InfoView (Thats good) although If I press the show Info Button the InfoView will appear suddenly on screen and ease out the right side of the screen, making it unreachable (off screen).
My question is there a way to compare the InfoView coordinates so that if its on Screen (And I haven't called it, run the show View) it could be sent back off screen. Like with an if statement. for example like:
if InfoView == CGPoint(12,20).... //(Then Run Hide View)
(I know the if statement is wrong) And I do understand that I got to run that func in the app delegate in the applicationWillResignActive.
Please can someone help me, am I on the right track or can someone give me another solution. (Tell Me If I need to explain it better)
You should compare it by below way. Do not comapre with hardcoded point.
if(InfoView.center.x > self.view.bounds.width)
{
//infoview is hidden
}
else
{
//infoview is visible
}

Prevent click through view in swift

I'm working in Xcode and swift, I created a view acting as a menu that toggles on tap, when the menu comes out I can still click a test button underneath it. I don't want that to happen. I want everything behind the view to be disabled giving priority to the menu view. (View Image below)
screenshot from the sample app
Keep in mind that I'm not considering one button, if that was the case I would've disabled that specific button. This page will be a scroll view and it will be dynamic.
this is the code that I'm using:
#IBAction func MenuButton(sender: UIButton) {
if self.MenuView.frame.origin.x == -180 {
UIView.animateWithDuration(0.5, animations:{
self.MenuView.frame = CGRectMake(self.MenuView.frame.origin.x + 180, self.MenuView.frame.origin.y, self.MenuView.frame.size.width, self.MenuView.frame.size.height)
})
} else {
UIView.animateWithDuration(0.5, animations:{
self.MenuView.frame = CGRectMake(self.MenuView.frame.origin.x - 180, self.MenuView.frame.origin.y, self.MenuView.frame.size.width, self.MenuView.frame.size.height)
})
}
}
the view is hidden 180 pixels on the left side, when the menu button is clicked the view will animate 180 pixels to the right which brings it to the front. The function checks if the view is already opened so it can animate it back 180 pixel to hide it.
The only thing I need is to disable clicking through the view.
Swift 3 version that worked great for me in stopping the click through (thanks to the previous comment by #Ismail).
myAddedSubView.isUserInteractionEnabled = true
This is the 'simple, one-line' answer and works like z index (presuming you wanted the z index to be 'absolute top'.
The fact that the button is still visible and clickable means that it must be in front of the menu. If you rearrange the order of things so that the menu is in front of the button, then you should get the result that you are looking for.

Replicate iOS 8.4 music app search animation

I am making an app with a search button in the nav bar. I would like to replicate the search animation in the music app.
I would like to press search and have the search bar come in from the top, and the table view to fade in.
Is there an easy way to do this?
I believe its pretty simple
It is actually a different view that is sliding down.
So essentially create a view with everything you want on it. Like the textField, the cancel button, etc.
This view when it is created needs to appear above your mainView.
So the y value on your frame will be 0 minus your new view height.
When you are ready for the view to appear use UIView.animateWithDuration to change your views frame y value to 0
UIView.animateWithDuration(2, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: {
myView.frame = CGRectMake(0, myView.frame.origin.y + myView.frame.height, 100, 100)
//could also just set the y value to 0 but I included the origin/height part to help you understand
}, completion: {
(value: Bool) in
})

Resources