Swift: UITextfield affecting button click? Unable to enter text due to animation? - ios

I am having an odd problem with my UIView animations and a UITextfield - I need to be able to click a button, have the button animate up to reveal a textfield so the user can enter text in the text field, then when the user presses that button again user interaction is disabled on the text field and the button moves back over.
This is what I mean-
I have accomplished this to an extent by placing the textfield behind the button and disabling it at the start, then when the button is clicked the button animates up to reveal the text field, which becomes enabled:
Problem is that the button click is being triggered (or possibly just the animation) when the user goes to edit the text field. So when they go to type, the button just moves back down and covers the text field.
This is odd because I have a boolean to set whether the button moves up or down, and I do not think this is being flipped because often the button moves BELOW the textfield, which should never happen.
Here is how I'm doing this:
#IBAction func enterText(sender: UIButton) {
print("time to enter text")
if !textOpen
{
UIView.animateWithDuration(0.5, animations: {
self.textBtn.center.y -= self.textBtn.bounds.height
})
happenedTxt.userInteractionEnabled = true
}
else {
UIView.animateWithDuration(0.5, animations: {
self.textBtn.center.y += self.textBtn.bounds.height
})
happenedTxt.userInteractionEnabled = false
}
textOpen = !textOpen
}
How can I trigger the button move ONLY when the button is pressed? Why is the textfield triggering the animation? Is there some call being made to return the button to its original position?

Related

Why is my hidden IOS button still selectable as an accessibility item?

I've got a UIButton in my IOS app that I'm trying to remove when users have Voiceover enabled. I'm able to hide it, but it still appears in the Voiceover swipe list AND is still selectable, if I happen to click in that area of the screen. I've tried:
self.chevronButton.isHidden = true
self.chevronButton.isEnabled = false
self.chevronButton.isAccessibilityElement = false
self.chevronButton.alpha = 0
self.chevronButton.accessibilityElementsHidden = true
UIAccessibility.post(notification: UIAccessibility.Notification.layoutChanged, argument: nil)
This only seems to make the button invisible. How can I remove it from the Voiceover swipe list and make it so that is cannot be selected by touch?

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.

On selecting TextField (opening keyboard) in ScrollView - content jumps down and back with keyboard

My TextField inside of ScrollView, when I select it, all content jumps firstly down, and returns back with keyboard.
ScrollView {
VStack {
TextField("Placeholder", $value)
Button()
.keyboardResponsive(enabled: true)
}
}
The reason was in property keyboardResponsiveEnabled of one of used on screen button. Parent class of it has keyboardResponsiveEnabled = true by default.
So if you have such problem, check all elements on screen with superclasses for this property.

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.

Xamarin IOS Change color of button momentarily

I'm creating a quiz application. The user selects an answer, get a brief confirmation whether the answer is correct or wrong before automatically is directed to the next question.
So, I have a view that has a label and 4 different buttons with different texts coming from database. When the user selects a button, it should flash or have a different background color for few seconds before the buttons are loaded with different text.
I have tried doing this in the touchUpInside. I set a different color for the button and I do Thread.Sleep(2000). This is my event handler of the TouchUpInside of one of the buttons:
partial void Answer(MonoTouch.Foundation.NSObject sender)
{
// if wrong answer, highlight to red and move on.
((UIButton)sender).backgroundColor = UIColor.Red;
Sleep(2000); // freeze to display the red color for 2 seconds
LoadNextQuestion(); // load the next question method
}
But the color only changes after the next question loads. How can I make it freeze there for 2 seconds?
thanks
You can do this with an animation:
button1.TouchDown += delegate(object sender, EventArgs e) {
UIView.Animate(2,0,UIViewAnimationOptions.Autoreverse,
() => { button2.BackgroundColor = UIColor.Yellow; },
() => { button2.BackgroundColor = UIColor.Green; });
};
Here button1 is triggering the animation of button2, but you could setup the trigger to be anything. This sample just animates the button's background color from Green to Yellow and back Again, with a 2s duration.

Resources