How to show and hide the keyboard with a subview - swift

I have a custom UIView that is a subview on a UIViewController.
I have it added in my storyboard and set it to Hidden.
My subview is also within another UIView that I'm using as a 'blur view' which is also initially Hidden.
I have functions that will unhide & hide the subviews.
My custom subview has a UITextField. I can show the keyboard and move the subview up with no problems. When I type in the keyboard or dismiss it my subview moves up and to the left. When I try to show my subview again it shows incorrectly (up and to the left).
The custom subview starts at the center of my screen.
The goal is move it up when the keyboard shows so it will not cover the subview or the UITextField, allow the user to type in the UITextField, and then dismiss the keyboard and move the custom subview back to the center.
In my UIViewController:
// Showing the custom sub view
func displayCustomSubView() {
if let window = UIApplication.shared.keyWindow {
self.blurView.isHidden = false
self.customSubView.isHidden = false
self.blurView.frame = window.frame
self.customSubView.center = window.center
window.addSubview(self.blurView)
UIApplication.shared.keyWindow?.bringSubviewToFront(self.blurView)
}
}
// Hiding the custom sub view
// the custom sub view has a button I tap to hide
#objc func dismissCustomSubView() {
self.blurView.isHidden = true
self.customSubView.isHidden = true
}
// Show Keyboard
// Since I am using the window to make sure my blur view expands to the full frame, I have tried just moving the window up
#objc func keyboardWillShow(sender: NSNotification) {
if let window = UIApplication.shared.keyWindow {
window.frame.origin.y = -75
}
}
// Hide Keyboard
#objc func keyboardWillHide(sender: NSNotification) {
if let window = UIApplication.shared.keyWindow {
window.frame.origin.y = 0
}
}
// Custom Subview Extension
extension CustomSubView: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
Added the Custom Subview Extension above.

First add this notification within your viewDidLoad(). And make a global variable called var keyboardH: CGFloat = 0:
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillShow),
name: UIResponder.keyboardWillShowNotification,
object: nil
)
And this function below:
#objc func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
self.keyboardH = keyboardHeight
}
This function is called every time the keyboard is present and will reveal the keyboard height and later we can use this variable.
So in your code:
#objc func keyboardWillShow(sender: NSNotification) {
if let window = UIApplication.shared.keyWindow {
let position = window.frame.origin.y - keyboardH
window.frame.origin.y = position
}
}

Related

Status button highlight when popover is active (Swift, macOS)

I am making a status bar macOS app. On clicking on the status bar icon, I am showing an NSPopover (not an NSMenu). However, when my NSPopover is shown, my status menu icon is not highlighted. It is only highlighted for a moment when I click it. I want it to stay highlighted, much like how it behaves with the wifi status bar icon.
I know that if I use a NSMenu instead of a NSPopover, it can probably be fixed. But the requirement is such that I need to use a NSPopover.
I have tried the following approaches, but to no avail:
1.
let statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength)
if let button = statusItem.button {
button.setButtonType(.pushOnPushOff)
}
statusItem.highlightMode = true
statusItem.button?.highlight(true)
statusItem.button?.isHighlighted = true
I am not very experienced with status bar apps. So I am not really sure about which approach to take here.
The left most icon is my status bar app icon. The popover is currently active but the icon is not highlighted. I had to crop out the popover.
This can be done, but to do it reliably requires some tight coupling. In this example, I assume you have a NSViewController named PopoverController that has a togglePopover() method and that you've set this controller as the target of the status bar button.
Step 0: Context
Here's the basic setup of the class that controls the popover:
final class PopoverController: NSViewController
{
private(set) var statusItem: NSStatusItem!
private(set) var popover: NSPopover
// You can set up your StatusItem and Popover anywhere; but here's an example with -awakeFromNib()
override func awakeFromNib()
{
super.awakeFromNib()
statusItem = NSStatusBar.system.statusItem(withLength: 20.0)
statusItem.button?.sendAction(on: [.leftMouseDown, .rightMouseDown])
statusItem.button?.image = NSImage(named: "statusBar-icon")
statusItem.button?.target = self
statusItem.button?.action = #selector(togglePopover)
popover = NSPopover()
popover.behavior = .transient
popover.delegate = self
popover.contentViewController = self
popover.setValue(true, forKeyPath: "shouldHideAnchor") // Private API
}
#objc func togglePopover()
{
if popover.isShown
{
popover.performClose(nil)
}
else if !popover.isShown,
let button: NSButton = statusItem.button,
button.window?.contentView != nil, // Exception thrown if view is nil or not part of a window.
button.superview != nil
{
popover.show(relativeTo: .zero, of: button, preferredEdge: .minY)
}
}
}
Step 1: Override the Status Button
extension NSStatusBarButton
{
public override func mouseDown(with event: NSEvent)
{
if event.modifierFlags.contains(.control)
{
self.rightMouseDown(with: event)
return
}
if let controller: PopoverController = self.target as? PopoverController
{
controller.togglePopover()
self.highlight(controller.popover.isShown)
}
}
}
Step 2: Handle Popover Closing
Make sure PopoverController conforms to NSPopoverDelegate and implement this delegate method:
func popoverDidClose(_ notification: Notification)
{
statusItem.button?.highlight(false)
}
Outcome
With all of that in place, the button highlighting now works just as it does for Apple's system status bar items like Control Center, Wifi, Battery, etc.
Note: you'll also need to add a global event monitor to listen for clicks that happen outside of your popover to ensure that it closes properly when the user clicks away from it. But that's outside the scope of this question and available elsewhere on SO.

Remove any view from any where e.g from window

I have 2 views on the screen, one is overlayView at the bottom and introView at the top of that overlayView. When I tap(tapToContinueAction) on the screen they should both hide or remove themselves.
extension UIView {
....
func hideView(view: UIView, hidden: Bool) {
UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: {
view.isHidden = hidden
})
}
}
class IntroScreen
#IBAction func tapToContinueAction(_ sender: UITapGestureRecognizer) {
self.hideView(view: self, hidden: true)
}
--
class OverlayView : UiView {
...
}
In current situation i can hide introScreen only and i dont know how the other class's action can effect the overlayView at the same time and hide that view as well. Any idea?
You have two different classes for your views. Make an extension of your window to remove your specific views just like I have made removeOverlay and removeIntroView both these computed properties will go and search in subviews list of window and check each view with their type and remove them. Thats how you can remove any view form any where.
class OverLayView: UIView {}
class IntroView: UIView {
#IBAction func didTapYourCustomButton(sender: UIButton) {
let window = (UIApplication.shared.delegate as! AppDelegate).window!
window.removeOverlay
window.removeIntroView
}
}
extension UIWindow {
var removeOverlay: Void {
for subview in self.subviews {
if subview is OverLayView {
subview.removeFromSuperview()// here you are removing the view.
subview.hidden = true// you can hide the view using this
}
}
}
var removeIntroView: Void {
for subview in self.subviews {
if subview is IntroView {
subview.removeFromSuperview()// here you are removing the view.
subview.hidden = true// you can hide the view using this
}
}
}
}

Swift Message Text Area

I am creating a new app and I want to add message area like:
When user click the text area keyboard will shown and user start writing message right button will be send button and text area with will be grow like:
What is the easy way to do it?
I hava a idea to do it but I think it is not the the best way. I am thinking to solve it programmaticly. In viewLoad I will create button and text area programmaticly then when user starts writing I will create them again.
As explained in the official apple documentation you can work with UITextField delegate methods to intercept the differents states.
For example you can use textFieldDidBeginEditing(_:) to handle the moment when the user start to edit. Here , you can handle constraints to enlarge your textField (pay attention, you must working with constraints if you use autolayout, dont make frame modifications):
func textFieldDidBeginEditing(textField: UITextField!) {
//
// hide photo button
// change mic button to send button
self.textFieldTrailingConstraint.constant = 10.0
UIView.animateWithDuration(0.5) {
self.view.layoutIfNeeded()
}
}
After that you can handle the other delegates:
func textFieldShouldEndEditing(textField: UITextField!) -> Bool {
return false
}
func textFieldShouldReturn(textField: UITextField!) -> Bool {
textField.resignFirstResponder()
return true
}
where you can repeat your animation or do whatever you want.
I'd also recommend to solve this in code. Loading the view you'll have to create all buttons and if the user starts writing you should recreate some of the buttons. But keep in Mind that you have to ensure, that the Textfield is the same instance or that you have to resign the responder to it by code.
In addition to textfield delegates, you need to handle keyboard as well:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector:"keyboardWillAppear:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector:"keyboardWillDisappear:", name: UIKeyboardWillHideNotification, object: nil)}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(true)
NSNotificationCenter.defaultCenter().removeObserver(self)}
func keyboardWillAppear(notification: NSNotification){
var userInfo:NSDictionary = notification.userInfo!
var keyboardSize:CGSize = userInfo.objectForKey(UIKeyboardFrameBeginUserInfoKey)!.CGRectValue().size
var contentInsets:UIEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 0)
self.tableView.contentInset = contentInsets
self.tableView.scrollIndicatorInsets = contentInsets
var messageFrame:CGRect = self.commentField.frame
messageFrame.origin.y -= keyboardSize.height
self.commentField.frame = messageFrame}
func keyboardWillDisappear(notification: NSNotification){
var userInfo:NSDictionary = notification.userInfo!
var keyboardSize:CGSize = userInfo.objectForKey(UIKeyboardFrameBeginUserInfoKey)!.CGRectValue().size
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(0.25)
self.tableView.contentInset = UIEdgeInsetsZero
UIView.commitAnimations()
self.tableView.scrollIndicatorInsets = UIEdgeInsetsZero
var messageFrame:CGRect = self.commentField.frame
messageFrame.origin.y += keyboardSize.height
self.commentField.frame = messageFrame}

How to slide TabBar up to hide it in tvOS app?

In my tvOS app I have a TabBarController with 3 viewControllers. What I want to do is to automatically hide/change focus of the tabBar when I switch to the next viewController.
I saw some posts here, on SO that suggested to change alfa on the tabBar, but I would like to have a slide up animation, same way as it does when you change focus to something in the viewController.
Any kind of help is highly appreciated.
As Charles said.. Something like this in the derived UITabBarController:
var focusOnChildVC : Bool = false {
didSet {
self.setNeedsFocusUpdate()
}
};
override weak var preferredFocusedView: UIView? {
get {
let v : UIView?;
let focused = UIScreen.mainScreen().focusedView
//A bit of a hack but seems to work for picking up whether the VC is active or not
if (focusOnChildVC && focused != nil) {
v = self.selectedViewController?.preferredFocusedView
} else {
//If we are focused on the main VC and then clear out of property as we're done with overriding the focus now
if (focusOnChildVC) {
focusOnChildVC = false
}
v = super.preferredFocusedView;
}
return v
}
}
The basic idea of the solution described below is to subclass UITabBarController and selectively use the super implementation of weak var preferredFocusedView: UIView? { get } or one that returns selectedViewController?.preferredFocusView along with an implementation of didUpdateFocusInContext(_:withAnimationCoordinator:) that sets up an NSTimer that triggers a focus update and sets a flag that controls the preferredFocusView implementation.
More verbosely, Subclass UITabBarController and override didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator). In your implementation (make sure to call the super implementation) you can inspect the context and determine if a descendent view of the tabBar property is the nextFocusedView or the previousFocusedView (and the nextFocusedView is not a descendent).
If the tab bar is gaining focus you can create an NSTimer for the duration that you want to show the tab bar before hiding it. If the tab bar loses focus before the timer fires, invalidate it. If the timer fires, call setNeedsFocusUpdate() followed by updateFocusIfNeeded().
The last piece you need to get this to work is a flag that is set to true while the timer is set. You then need to override weak var preferredFocusedView: UIView? { get } and call the super implementation if the flag is false and if it is true return selectedViewController?.preferredFocusedView.
You can do it in a UITabBarController subclass:
final class TabBarViewController: UITabBarController {
private(set) var isTabBarHidden = false
func setTabBarHidden(_ isHidden: Bool, animated: Bool) {
guard isTabBarHidden != isHidden else {
return
}
var frame: CGRect
let alpha: CGFloat
if isHidden {
frame = tabBar.frame
frame.origin.y -= frame.height
alpha = 0
} else {
frame = tabBar.frame
frame.origin.y += frame.height
alpha = 1
}
let animations = {
self.tabBar.frame = frame
self.tabBar.alpha = alpha
}
if animated {
UIView.animate(withDuration: 0.3, animations: animations)
} else {
animations()
}
isTabBarHidden = isHidden
}
}

Set textfield in UISearchBar

I'm trying to add SearchController to UINavigationBar. I'm trying to set UITextField of UISearchBar after back button of UINavigationController. I want some more space after back button
When I start searching it appears as
Whereas I should be able to view back button. Only textfield width should be decreased. Also after Cancel it should be again back to initial layout. Whereas it is displayed as below:
Below is my code
var searchResultController = UISearchController()
self.searchResultController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
controller.searchBar.delegate = self
controller.hidesNavigationBarDuringPresentation = false
self.navigationController?.navigationBar.addSubview(controller.searchBar)
return controller
})()
override func viewDidAppear(animated: Bool) {
for subView in searchResultController.searchBar.subviews{
for subsubView in subView.subviews {
if let textField = subsubView as? UITextField {
var bounds: CGRect
bounds = textField.frame
bounds.size.width = self.view.bounds.width - 50
}
}
}
}
Please let me know how can I fix this.
Thanks in advance
For setting the UIsearchBar add it to Navigation's titleView as
self.navigationItem.titleView = controller.searchBar
For removing Cancel button we can use UISearchControllerDelegate method
func didPresentSearchController(searchController: UISearchController) {
searchController.searchBar.showsCancelButton = false
}
Hope this may help any one.