I want to hide a FabButton everytime a floatingViewController appears and show it again when I close the view.
I tried hiding the FabButton itself with
self.menuButton.hidden = true
but there isn't a callback function when I close the floatingViewController so I dont have a way to un hide it.
I also tried setting the zPosition manually but the button is unaffected
Is there a better approach to this?
The NavigationBarViewController has delegation methods for detecting the state of the floatingViewController. Set the delegate object,
navigationBarViewController?.delegate = self
Then try out the delegate methods:
extension AppNavigationBarViewController: NavigationBarViewControllerDelegate {
/// Delegation method that executes when the floatingViewController will open.
func navigationBarViewControllerWillOpenFloatingViewController(navigationBarViewController: NavigationBarViewController) {
print("Will Open")
}
/// Delegation method that executes when the floatingViewController will close.
func navigationBarViewControllerWillCloseFloatingViewController(navigationBarViewController: NavigationBarViewController) {
print("Will Close")
}
/// Delegation method that executes when the floatingViewController did open.
func navigationBarViewControllerDidOpenFloatingViewController(navigationBarViewController: NavigationBarViewController) {
print("Did Open")
}
/// Delegation method that executes when the floatingViewController did close.
func navigationBarViewControllerDidCloseFloatingViewController(navigationBarViewController: NavigationBarViewController) {
print("Did Close")
}
}
You should be able to use these methods to achieve the desired effect.
add this method to the FloatingViewController class
override func viewWillDisappear(animated: Bool) {
let vc = ...assign to the viewcontroller that holds tour menu button
vc.menuButton.hidden = false
}
Related
I want to check whenever the user swipes a popped viewController away. So for example when in whatsApp a user exits the current chat by swiping from the edge. How is that possible in Swift?
I don't want to use viewDidDisappear, because this method also gets called when another viewController is presented over the current viewController.
As I wrote in comment, a simple workaround would be in viewDidDisappear, check if the navigationController is nil.
class MyVc: UIViewController {
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if navigationController == nil {
print("view controller has been popped")
}
}
}
Of course, this solution works only if the view controller is embedded into a navigation controller, otherwise the if statement will always be true.
This "swipe" is handled by the interactivePopGestureRecognizer of the UINavigationController. It is possible to set the delegate of this gesture recognizer to your UIViewController as follows:
navigationController?.interactivePopGestureRecognizer?.delegate = self
Then, you can implement the UIGestureRecognizerDelegate in your UIViewController. This would look like this:
extension YourViewController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard gestureRecognizer.isEqual(self.navigationController?.interactivePopGestureRecognizer) else { return true }
print("The interactive pop gesture recognizer is being called.")
return true
}
}
I haven't tested the code, but this should print every time the interactivePopGestureRecognizer is used.
For more information, refer to the documentation of the interactivePopGestureRecognizer and UIGestureRecognizerDelegate.
I'm trying to react to the event that a UIPickerView started moving (not when the row was already selected).
I have searched throughout the delegate methods, and none helped. I also tried to register a notification, but couldn't figure out any that would notify as the user puts his finger on the component and starts scrolling.
Any ideas of what alternatives are there?
You can create a custom class of UIPickerView and override hitTest(point:with:). Creating a protocol, you can send the current picker through a delegate method to your controller and draw whatever you like:
protocol CustomPickerViewDelegate: class {
func didTapped(_ picker: CustomPickerView)
}
class CustomPickerView: UIPickerView {
weak var myDelegate: CustomPickerViewDelegate?
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// Only test for points in your needed view
if !self.point(inside: point, with: event) {
return nil
}
// Return using CustomPickerViewDelegate the current picker
// that can be used to determine which one was selected
myDelegate?.didTapped(self)
// Call super.hitTest(_: with:)
return super.hitTest(point, with: event)
}
}
Do NOT forget (in your controller: eg. YourViewController):
self.pickerView.myDelegate = self.
Create an extension of your controller the subscribes to CustomPickerViewDelegate protocol:
extension YourViewController: CustomPickerViewDelegate {
func didTapped(_ picker: CustomPickerView) {
// do what you want here
self.addBorderTo(picker: picker)
}
}
If you like you can extend the UIPickerViewDelegate (see below how you can extend base class delegate)
Extending a delegate from a base class
Good luck :]
I need to check if the back button is pressed in Swift. The action does not fire programmatically, so the approach appears to be to check if self is in the viewcontroller stack. I think the solution is here:
How to check for self with contains() in Swift?
However for each of the answers Swift tells me that contains is an unresolved identifier. I understand that is it in the Swift standard library so I assume I don't know how to use it.
Therefore what is the proper form of the following function?
override func viewDidDisappear(_ animated: Bool) {
if let viewControllers = self.navigationController?.viewControllers as? [UIViewController] {
if (contains(viewControllers, self)) {
print("Back button not pressed")
} else {
print ("Back button pressed")
}
}
}
Similar to RAJAMOHAN-S I look at where I'm transitioning from. However, the function
override func willMove(toParentViewController parent: UIViewController?)
provides a nice way of doing that; I combined with a simple bool variable to differentiate if the back button was really used or whether another programmatic method meant a transition event.
override func willMove(toParentViewController parent: UIViewController?) {
super.willMove(toParentViewController: parent)
if parent == nil {
// The back button was pressed, can be acted on
}
}
else{
print ("Coming here from a child view")
}
}
I want to open an other view controller, after checking if it is the first run of the app.
It works when I press a button but not when I call the method openMap
class TutorialController: UIViewController {
override func viewDidLoad() {
//check if the app opens for the first time
if(UserDefaults.standard.bool(forKey: "HasLaunchedOnce"))
{
// app already launched
print("not first launch")
openMap()
}
else
{
// This is the first launch ever
UserDefaults.standard.set(true, forKey: "HasLaunchedOnce")
UserDefaults.standard.synchronize()
print("first launch")
openTutorial()
}
}
func openTutorial(){
}
#IBAction func openMap(){
print("openmap opened")
performSegue(withIdentifier: "openMap", sender: nil)
}
}
I assume, you've connected your button to #IBAction func openMap()
if so, you should not call openMap() action inside your viewDidLoad, but use the same code performSegue(withIdentifier: "openMap", sender: nil) instead in your viewDidAppear:
if(UserDefaults.standard.bool(forKey: "HasLaunchedOnce"))
{
// app already launched
print("not first launch")
performSegue(withIdentifier: "openMap", sender: nil)
}
...
If it doesn't work, you've probably made a mistake with creation of your segue and have connected Button to the destination ViewController directly in your storyboard instead of connecting two controllers:
If so, just remove the old segue, and re-crete it in the way as it is on the image above and assign the same segue id "openMap"
EDITED:
Please, move performing of your segue to the viewDidAppear instead of viewDidLoad, because viewDidLoad is called when the ViewController object is created and it's not yet attached to the window.
Ok, from what I understand is that you want to perform a segue "openMap" when it HasLaunchedOnce. Well what you're doing wrong is that you're calling an #IBAction func. This is my suggestion
if you still want to have that button
create a function and name if whatever you want. Inside this function perform this segue. Link this function to the if else statement and the button.
eg:
//if else statement
if(UserDefaults.standard.bool(forKey: "HasLaunchedOnce"))
{
// app already launched
print("not first launch")
anotherFunction()
}
//#ibaction (scrap this if you don't want the button)
#IBAction func openMap()
{
print("openmap opened")
anotherFunction()
}
//another function
func anotherFunction()
{
performSegue(withIdentifier: "openMap", sender: nil)
}
hope this helps
In my application, the first view that is launched is controlled by RootUIViewController. Now the user can tap on any of the buttons on this view and then segue to a view controlled by LeafUIViewController. Each button points to this same view, with different value for some of the arguments.
Now I have implemented the 3D Touch shortcut menu which correctly calls the following function in the AppDelegate:
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void)
In this function, I want to go to the LeafUIViewController such that the user can still navigate back to the RootViewController.
What is the right way to do this so that the Root is correctly instantiated, pushed on stack and then view navigates to the Leaf?
I suggest against doing any launch actions specific segues from that callback. I usually set a global state and handle it in all my relevant viewcontrollers. They usually pop to the main viewcontroller. In there I do programatically do the action just as would the user normally do.
The advantage is that the viewcontroller hierarchy is initialised in a normal way. The state can be then handled by the first viewcontroller that's going to be displayed on screen which isn't necessarily the first viewcontroller of the app. The application could be already initialised when the user triggers the action. So there could be a random viewcontroller in the hierarchy.
I use something like:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if #available(iOS 9.0, *) {
if let shortcutItem = launchOptions?[UIApplicationLaunchOptionsShortcutItemKey] as? UIApplicationShortcutItem {
handleShortcutItem(shortcutItem)
}
}
return true
}
enum ShortcutType: String {
case myAction1 = "myAction1"
case myAction2 = "myAction2"
}
#available(iOS 9.0, *)
func handleShortcutItem(shortcutItem: UIApplicationShortcutItem) -> Bool {
if let shortcutType = ShortcutType.init(rawValue: shortcutItem.type) {
switch shortcutType {
case .myAction1:
MyHelper.sharedInstance().actionMode = 1
return true
case .myAction2:
MyHelper.sharedInstance().actionMode = 2
return true
default:
return false
}
}
return false
}
and then in main viewController (such as main menu) handle the action somehow:
override func viewDidAppear() {
super.viewDidAppear()
switch MyHelper.sharedInstance().actionMode {
case 1:
// react on 1 somehow - such as segue to vc1
case 2:
// react on 2 somehow - such as segue to vc2
default:
break
}
// clear the mode
MyHelper.sharedInstance().actionMode = 0
}
And in other vc's:
override func viewDidLoad() {
super viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "reloadView", name: UIApplicationWillEnterForegroundNotification, object: nil)
}
func reloadView() {
if MyHelper.sharedInstance().actionMode {
self.navigationController.popToRootViewControllerAnimated(false)
}
}
This might not be the best if you are using several vc's. If there is something better, I'love to learn that :)