Reputation: 43
I'm trying to create a custom chat toolbar to display a text view with the message, along with buttons to allow photo selection. I reworked my code this morning to very closely follow this WWDC 2017 talk on text input. The toolbar itself is a UIInputViewController, which uses a custom view to define it's inputView. When I select a button, I present a UIImagePickerController, which works fine. The problem occurs when I choose an image and return back to the conversation screen... at this point when I interact with the toolbar again, it disappears on touch. Here is the stack trace right before if is removed from screen:
#0 0x000000010d3b91fa in ConvoViewController.textViewDidEndEditing(UITextView) -> () at /Users/Joel/Documents/Software Projects/AtMe/AtMe/ConvoViewController.swift:480
#1 0x000000010d3b93ca in @objc ConvoViewController.textViewDidEndEditing(UITextView) -> () ()
#2 0x000000010f4b88b8 in -[UITextView resignFirstResponder] ()
#3 0x000000010ea9cc74 in -[UIView(Hierarchy) _removeFirstResponderFromSubtree] ()
#4 0x000000010ea9d2eb in __UIViewWillBeRemovedFromSuperview ()
#5 0x000000010ea9d09e in -[UIView(Hierarchy) removeFromSuperview] ()
#6 0x000000010f45cf5c in __55-[UIInputWindowController invalidateInputAccessoryView]_block_invoke ()
#7 0x000000010ec069a5 in -[UIResponder _preserveResponderOverridesWhilePerforming:] ()
#8 0x000000010f45ced4 in -[UIInputWindowController invalidateInputAccessoryView] ()
#9 0x000000010f45d889 in -[UIInputWindowController changeToInputViewSet:] ()
#10 0x000000010f456675 in -[UIInputWindowController moveFromPlacement:toPlacement:starting:completion:] ()
#11 0x000000010f45e82f in -[UIInputWindowController setInputViewSet:] ()
#12 0x000000010f45614c in -[UIInputWindowController performOperations:withAnimationStyle:] ()
#13 0x000000010f0cca8a in -[UIPeripheralHost(UIKitInternal) setInputViews:animationStyle:] ()
#14 0x000000010ec0962d in -[UIResponder(UIResponderInputViewAdditions) reloadInputViews] ()
#15 0x000000010ec07ef1 in -[UIResponder _windowBecameKey] ()
#16 0x000000010ea6e9bb in -[UIWindow _makeKeyWindowIgnoringOldKeyWindow:] ()
#17 0x000000010ef33ab4 in -[UITextInteractionAssistant(UITextInteractionAssistant_Internal) setFirstResponderIfNecessary] ()
#18 0x000000010ef37178 in -[UITextInteractionAssistant(UITextInteractionAssistant_Internal) oneFingerTap:] ()
#19 0x000000010ef24f59 in -[UIGestureRecognizerTarget _sendActionWithGestureRecognizer:] ()
#20 0x000000010ef2cd57 in _UIGestureRecognizerSendTargetActions ()
#21 0x000000010ef2a70b in _UIGestureRecognizerSendActions ()
#22 0x000000010ef299ce in -[UIGestureRecognizer _updateGestureWithEvent:buttonEvent:] ()
#23 0x000000010ef16152 in _UIGestureEnvironmentUpdate ()
#24 0x000000010ef15c43 in -[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] ()
#25 0x000000010ef14e0a in -[UIGestureEnvironment _updateGesturesForEvent:window:] ()
#26 0x000000010ea60eea in -[UIWindow sendEvent:] ()
#27 0x000000010ea0da84 in -[UIApplication sendEvent:] ()
#28 0x000000010f1f15d4 in __dispatchPreprocessedEventFromEventQueue ()
#29 0x000000010f1e9532 in __handleEventQueue ()
#30 0x0000000110d14c01 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
#31 0x0000000110cfa0cf in __CFRunLoopDoSources0 ()
#32 0x0000000110cf95ff in __CFRunLoopRun ()
#33 0x0000000110cf9016 in CFRunLoopRunSpecific ()
#34 0x00000001135fba24 in GSEventRunModal ()
#35 0x000000010e9f0134 in UIApplicationMain ()
#36 0x000000010d3ebed7 in main at /Users/Joel/Documents/Software Projects/AtMe/AtMe/AppDelegate.swift:15
#37 0x000000011258065d in start ()
#38 0x000000011258065d in start ()
I get the feeling that the UIInputViewController is invalidated in some way, but I don't know how to test this. Does anyone have any ideas? I have spent multiple days trying to make this toolbar work bug free, and it seems impossible no matter how you approach it.
conversationViewController.swift
// MARK: - Input Accessory View (Controller)
fileprivate let chatToolbarView: ChatToolbarView = {
let view = ChatToolbarView(frame: CGRect.zero, inputViewStyle: UIInputViewStyle.default)
// Add selectors as targets to the toolbar buttons
view.sendButton.addTarget(self, action: #selector(didPressSend(sender:)), for: UIControlEvents.touchUpInside)
view.libraryButton.addTarget(self, action: #selector(didPressLibraryIcon(sender:)), for: UIControlEvents.touchUpInside)
view.cameraButton.addTarget(self, action: #selector(didPressCameraIcon(sender:)), for: UIControlEvents.touchUpInside)
return view
}()
// Wrapper view controller for the custom input accessory view
fileprivate let chatToolbarViewController = UIInputViewController()
override var inputAccessoryViewController: UIInputViewController? {
// Ensure our input accessory view controller has it's input view set
chatToolbarViewController.inputView = chatToolbarView
// Return our custom input accessory view controller. You could also just return a UIView with
// override func inputAccessoryView()
return chatToolbarViewController
}
/** Action method which fires when the user taps the camera icon. */
@objc func didPressCameraIcon(sender: Any) {
photoPickerDisplayed = true
// Create picker, and set this controller as delegate
let picker = UIImagePickerController()
picker.delegate = self
picker.allowsEditing = true
picker.sourceType = UIImagePickerControllerSourceType.camera
DispatchQueue.main.async {
self.present(picker, animated: true, completion: nil)
}
}
override var canBecomeFirstResponder: Bool { return true }
Upvotes: 3
Views: 1521
Reputation: 43
I ended up solving the issue by assigning my custom toolbar view to the view controller's inputAccessoryView, as opposed to assigning it to the inputView property of the view controller's inputAccessoryViewController.
The big issue was that with the original code, any view controllers presented modally (eg. UIImagePickerViewController) confused the inputAccessoryViewController, and would be dismissed entirely upon return to the original controller. Using the controller seemed to prove unnecessary anyways, it is more for direct keyboard customization as opposed to accessories.
The code now looks like this:
fileprivate let chatToolbarView: ChatToolbarView = {
let view = ChatToolbarView(frame: CGRect.zero, inputViewStyle: UIInputViewStyle.default)
// Add selectors as targets to the toolbar buttons
view.sendButton.addTarget(self, action: #selector(didPressSend(sender:)), for: UIControlEvents.touchUpInside)
view.libraryButton.addTarget(self, action: #selector(didPressLibraryIcon(sender:)), for: UIControlEvents.touchUpInside)
view.cameraButton.addTarget(self, action: #selector(didPressCameraIcon(sender:)), for: UIControlEvents.touchUpInside)
return view
}()
/** Provide the view controller's inputAccessoryView object. */
override var inputAccessoryView: UIView? { return chatToolbarView }
/** Give the view controller permission to become first responder. */
override var canBecomeFirstResponder: Bool { return true }
Upvotes: 1
Reputation: 2099
On you viewDidAppear
yourTextView.becomeFirstResponder()
This will trigger your custom inputView to open again.
Hope this helps!
Upvotes: 0