Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set content of the menu programmatically #44

Closed
thang2410199 opened this issue Jul 7, 2016 · 18 comments
Closed

Set content of the menu programmatically #44

thang2410199 opened this issue Jul 7, 2016 · 18 comments

Comments

@thang2410199
Copy link
Contributor

        let menuLeftNavigationController = UISideMenuNavigationController(rootViewController: SideMenuViewController())
        menuLeftNavigationController.leftSide = true
        // UISideMenuNavigationController is a subclass of UINavigationController, so do any additional configuration of it here like setting its viewControllers.
        SideMenuManager.menuLeftNavigationController = menuLeftNavigationController

The code above let me open the side menu, but its empty. It should have root controller = SideMenuViewController(). It never loaded.

I tested SideMenuViewController and it display just fine anywhere else.

@jonkykong
Copy link
Owner

You either need to set the rootViewController in code or in your storyboard. SideMenu itself is just a navigation controller. Please see the demo project for additional insight.

@thang2410199
Copy link
Contributor Author

thang2410199 commented Jul 8, 2016

I did set the rootViewController using
UISideMenuNavigationController(rootViewController: SideMenuViewController())

SideMenuViewController is my custom view controller.
I fully understand that SideMenu is subclass of navigation controller.

I have to use setViewControllers to make it works as expected, it is ugly to write.

@jonkykong
Copy link
Owner

Try doing more testing/debugging to assess root cause; I don't have enough to go on to tell you what's wrong with the info you've provided.

Add breakpoints in ViewDidLoad to see if SideMenuViewController is actually loading. Try subclassing UISideMenuNavigationController and overriding its init methods to see how rootViewController is being set. Try debugging the view hierarchy to see if anything is initialized but not visible.

@thang2410199
Copy link
Contributor Author

thang2410199 commented Jul 11, 2016

let menuLeftNavigationController = UISideMenuNavigationController(rootViewController: SideMenuViewController())

let navigationController = UINavigationController(rootViewController: SideMenuViewController())

After those statement run, debugger shows:

(lldb) print menuLeftNavigationController.topViewController
(UIViewController?) $R0 = nil
(lldb) print navigationController.topViewController
(UIViewController?) $R1 = 0x000000015eebd790 {
  UIKit.UIResponder = {
    ObjectiveC.NSObject = {}
  }
}

SideMenuViewController never loaded in the first statement.

I sub classed UINavigationController just like you do, the topViewController of my sub class is not nil, so I think there is problem with your implementation

@thang2410199
Copy link
Contributor Author

I found the problem:

when init with rootViewController, the pushViewControll function is called, you check if menuviewcontroller is nil or not. At init time it always nil. So no code is run.

I will submit a PR to fix it if you dont have time, its solved 👍

@jonkykong
Copy link
Owner

@thang2410199 can you point me to the file/line number?

@jonkykong jonkykong reopened this Jul 11, 2016
@thang2410199
Copy link
Contributor Author

https://github.com/jonkykong/SideMenu/blob/master/Pod/Classes/UISideMenuNavigationController.swift#L118

I created a PR here #46

I am learning iOS, so it will be really good if I can contribute to your project to learn in the process.

@jonkykong
Copy link
Owner

I appreciate your earnest in wanting to learn contributing to this repo! I came up with a fix for this myself, however I took a different approach. Let's sync on functional expectations of the menu before taking either one.

When I used the code snippet you provided at the outset of this issue, and then wire up a button to present the menu programmatically, the menu still appears but without a viewController.

From your PR, it seems that you are having problems getting the menu to appear at all because of the missing rootViewController.

In UISideMenuNavigationController, I added these lines which successfully set the rootViewController:

    // TODO: these initializers shouldn't be necessary, but they are failing to set 
    // the rootViewController for this custom class (on at least iOS 9.3)
    override public init(rootViewController: UIViewController) {
        super.init(rootViewController: rootViewController)

        viewControllers = [rootViewController]
    }

    required override public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }

@thang2410199
Copy link
Contributor Author

Wow, I did try your approach first hand, but I think write

viewControllers = [rootViewController]

is kind of a hack (we are doing the job of super class!). And at the
same time I think not call super class 's method on pushViewController
is not expected.

If the menu view controller is created, and navigate to a second
controller, then assign to left menu, it wont show second view
controller because the super's pushViewController is not called

Best Regard
Ngo Quoc Thang
http://iwindroid.com
+358 449648202

On Mon, Jul 11, 2016 at 10:39 PM, Jon Kent notifications@github.com wrote:

I appreciate your earnest in wanting to learn contributing to this repo! I
came up with a fix for this myself, however I took a different approach.
Let's sync on functional expectations of the menu before taking either one.

When I used the code snippet you provided at the outset of this issue, and
then wire up a button to present the menu programmatically, the menu still
appears but without a viewController.

From your PR, it seems that you are having problems getting the menu to
appear at all because of the missing rootViewController.

In UISideMenuNavigationController, I added these lines which successfully
set the rootViewController:

// TODO: these initializers shouldn't be necessary, but they are failing to set
// the rootViewController for this custom class (on at least iOS 9.3)
override public init(rootViewController: UIViewController) {
    super.init(rootViewController: rootViewController)

    viewControllers = [rootViewController]
}

required override public init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#44 (comment),
or mute the thread
https://github.com/notifications/unsubscribe/ACUvOJnnhyyWqA5BfXcAwQOvgjEkaubkks5qUpvngaJpZM4JHF6l
.

@jonkykong
Copy link
Owner

I completely agree it's hacky, but I don't see any other reason why letting the superclass work as normal doesn't work.. I've put breakpoints in all over the code to see where else I might be clearing out the rootViewController and nothing is being called that would seem to indicate a bug.

So are you saying your fix demonstrates that you see the rootViewController as expected?

@thang2410199
Copy link
Contributor Author

thang2410199 commented Jul 11, 2016

yes, I do. I debug and see the problem: when init with rootViewController, the framework actually called pushViewController. But your override method did not call super's pushViewController, so in the end there is no view controller added to navigation stack

@jonkykong
Copy link
Owner

Understood! Good find.

The reason why pushViewController is overridden is so that if you try to push a viewController from the side menu, it will instead defer that to the main screen. It appears that by overriding it that breaks the init method which uses the override to set itself up.

Here's how I would write the fix (note that I've learned a bit more about guard statements since I wrote this repo and I think it's syntactically cleaner to do it this way:

override public func pushViewController(viewController: UIViewController, animated: Bool) {
        guard viewControllers.count > 0 else {
            // NOTE: pushViewController is called by init(rootViewController: UIViewController)
            // so we must perform the normal super method in this case.
            super.pushViewController(viewController, animated: true)
            return
        }

        guard let menuViewController: UINavigationController = SideMenuTransition.presentDirection == .Left ? SideMenuManager.menuLeftNavigationController : SideMenuManager.menuRightNavigationController else {
            return
        }

        guard let presentingViewController = menuViewController.presentingViewController as? UINavigationController else {
            menuViewController.presentViewController(viewController, animated: animated, completion: nil)
            print("SideMenu Warning: cannot push a ViewController from a ViewController without a NavigationController. It will be presented it instead.")
            return
        }

        // to avoid overlapping dismiss & pop/push calls, create a transaction block where the menu
        // is dismissed after showing the appropriate screen
        CATransaction.begin()
        CATransaction.setCompletionBlock( { () -> Void in
            self.dismissViewControllerAnimated(true, completion: nil)
            self.visibleViewController?.viewWillAppear(false) // Hack: force selection to get cleared on UITableViewControllers when reappearing using custom transitions
        })

        UIView.animateWithDuration(SideMenuManager.menuAnimationDismissDuration, animations: { () -> Void in
            SideMenuTransition.hideMenuStart()
        })

        if SideMenuManager.menuAllowPopIfPossible {
            for subViewController in presentingViewController.viewControllers {
                if subViewController.dynamicType == viewController.dynamicType {
                    presentingViewController.popToViewController(subViewController, animated: animated)
                    CATransaction.commit()
                    return
                }
            }
        }
        if !SideMenuManager.menuAllowPushOfSameClassTwice {
            if presentingViewController.viewControllers.last?.dynamicType == viewController.dynamicType {
                CATransaction.commit()
                return
            }
        }

        presentingViewController.pushViewController(viewController, animated: animated)
        CATransaction.commit()
    }

@thang2410199
Copy link
Contributor Author

agree, better to read compare to multi level of statements.

Btw, you tell me more about the transition (e.g fading) as I interact with the menu (swipe). My way of thinking is that I will observe the offset (?) of the moving controller, and change the alpha of the menu accordingly, I dig into your code and find only SideMenuTransition class. I want to add parallax effect to the side menu.

@jonkykong
Copy link
Owner

Actually, one more change. One of those guard statements isn't necessary and was left over from a refactor I did a while back.

    override public func pushViewController(viewController: UIViewController, animated: Bool) {
        guard viewControllers.count > 0 else {
            // NOTE: pushViewController is called by init(rootViewController: UIViewController)
            // so we must perform the normal super method in this case.
            super.pushViewController(viewController, animated: true)
            return
        }

        guard let presentingViewController = presentingViewController as? UINavigationController else {
            presentViewController(viewController, animated: animated, completion: nil)
            print("SideMenu Warning: cannot push a ViewController from a ViewController without a NavigationController. It will be presented it instead.")
            return
        }

        // to avoid overlapping dismiss & pop/push calls, create a transaction block where the menu
        // is dismissed after showing the appropriate screen
        CATransaction.begin()
        CATransaction.setCompletionBlock( { () -> Void in
            self.dismissViewControllerAnimated(true, completion: nil)
            self.visibleViewController?.viewWillAppear(false) // Hack: force selection to get cleared on UITableViewControllers when reappearing using custom transitions
        })

        UIView.animateWithDuration(SideMenuManager.menuAnimationDismissDuration, animations: { () -> Void in
            SideMenuTransition.hideMenuStart()
        })

        if SideMenuManager.menuAllowPopIfPossible {
            for subViewController in presentingViewController.viewControllers {
                if subViewController.dynamicType == viewController.dynamicType {
                    presentingViewController.popToViewController(subViewController, animated: animated)
                    CATransaction.commit()
                    return
                }
            }
        }
        if !SideMenuManager.menuAllowPushOfSameClassTwice {
            if presentingViewController.viewControllers.last?.dynamicType == viewController.dynamicType {
                CATransaction.commit()
                return
            }
        }

        presentingViewController.pushViewController(viewController, animated: animated)
        CATransaction.commit()
    }

Feel free to put this in your PR and I will take it to give you credit for your find/work. Also please remove any excess formatting changes you did outside of this method.

Re: parallax, it depends on what you mean. Check out SideMenuManager.menuParallaxStrength to see if that's the effect you were going for (run it on your device, not simulator to see properly).

@thang2410199
Copy link
Contributor Author

Thanks, I will do it tomorrow.

About the parallax, its demonstrated in Twitch app (iOS), their left side menu is what I mean by "parallax". the menuParallaxStrength made me confused, but I will give it more try.

@jonkykong
Copy link
Owner

I see. The effect you're looking for isn't built into SideMenu currently. You want a modified version of the "In + Out" effect, which would partially slide the menu in as it is revealed. You could build this by either modifying the repo or writing your own subclasses.

The trouble with this effect is picking smart defaults, or making them configurable for how much you want the menu to slide as its revealed.

I believe the iOS push effect slides the covered screen by either 1/3 or 1/2 of its width. Perhaps this might be a good benchmark for you.

jonkykong added a commit that referenced this issue Jul 14, 2016
jonkykong added a commit that referenced this issue Jul 14, 2016
* tag '1.1.8':
  Updated pod spec and README for cocoa pods.
  Fix to prevent side menu from responding to user interaction while being animated in or out.
  fix #44
@jonkykong
Copy link
Owner

Thanks for contributing @thang2410199!

@stefanlam88
Copy link

stefanlam88 commented Feb 25, 2017

hi bro, how do actually set rootview controller when tap tableview row
. i use pushviewcontroller..it keep navigate

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants