exyte / fan-menu

Menu with a circular layout based on Macaw
MIT License
728 stars 58 forks source link

Change items array and re render #48

Closed kier70 closed 5 years ago

kier70 commented 5 years ago

I have a custom fan menu based on CustomViewController.swift

I need to use the menu in a non standard way where a user is presented with a first menu and on press will present a second menu.

I have successfully closed the menu and switched the items but on opening the items are not still showing the first menu unless I use updateNode which completely re-instantiates the menu and places it according to the original position, which is not required at this point. (I moved the whole fanmenu on open)

I tried implementing an updateScene function but this in turn eventually places the menu in it's original position.

The updateScene tried:

func updateScene() { var oContent = self.scene!.node.contents let buttonsNode = Group(contents: items.enumerated().map { (arg) -> Node in let (index, item) = arg return CustomButtonsScene(customMenu: self).createCustomButton(customMenu: self, data: item, index: index) }) oContent[0] = buttonsNode self.scene!.node.contents = oContent }

func updateScene() { var oContent = self.scene!.node.contents let menuButton = CustomMenuButtonScene(radius: radius) oContent[0] = menuButton.node self.scene!.node.contents = oContent }

Is there a way to render different items or change the class I previously set in IB?

shipinev commented 5 years ago

Hey @kier70. Could you please attach your project or maybe share your repo? It's quite hard to understand this issue without source code. Thanks!

kier70 commented 5 years ago

Thanks, the relevant code is below. I'm basically looking at an observable items property which accepts the new value as the buttons. Any help appreciated.

var items: [(String, String)] = [] {
        didSet {
            if let s = self.scene {
                let buttonsNode: Group = Group(contents: items.enumerated().map { (arg) -> Node in
                    let (index, item) = arg
                    return (s.buttonsGroup.createCustomButton(customMenu: self, data: item, index: index))
                })
                buttonsNode.opacity = 0.0
                buttonsNode.place = Transform.identity.scale(sx: 0.4, sy: 0.4)
                s.buttonsGroup.node.contents.removeAll()
                s.buttonsGroup.node.contents.append(buttonsNode)
                let animationGroup = Group()
                buttonsNode.contents.append(animationGroup)
            }
        }
    }
shipinev commented 5 years ago

Thanks for the listing. Unfortunately, I can't run your code without the entire project, but here is my approach which allows you to switch menu items on the fly.

Add switchItems method to FanMenu class:

func switchItems() {
    guard let scene = self.scene else {
        updateNode()
        return
    }
    scene.buttonsNode.contents.removeAll()
    scene.buttonsNode.contents.append(contentsOf: items.map {
        return FanMenuScene.createFanButtonNode(button: $0, fanMenu: self)
    })
    scene.updateMenu(open: isOpen)
}

After that change didSet property observer for items variable:

public var items: [FanMenuButton] = [] {
    didSet {
        switchItems()
    }
}

Now you can do something like this:

fanMenu.onItemDidClick = { button in
    if button.id == "myButtonId" {
        self.fanMenu.close()
        self.fanMenu.items = [
            FanMenuButton(
                id: "newMenuItem",
                image: UIImage(named: "icon"),
                color: Color(val: 0x000000)
            )
        ]
        self.fanMenu.open()
    }
}
kier70 commented 5 years ago

Thanks @shipinev, that's exactly what I have except I had not seen the updateMenu function.

I'll give it a try now and get back to you.

kier70 commented 5 years ago

Apologies but I cannot seem to get my head around this. I've attached a pared down project. What I'm trying to achieve is on selection of the button it changes the item without moving from the centre of the screen. test.zip

shipinev commented 5 years ago

I want to suggest you change displayNewMenu method behavior. Currently, you are trying to close the menu while changing menu items. I think that you can do something like this:

func displayNewMenu(i: [MenuItemEnum]) {
    buttonsNode.opacityVar.animation(to: 0, during: customMenu.duration)
        .easing(Easing.easeOut)
        .onComplete { [unowned self] in
            self.customMenu.items = MenuItem().GenerateFromEnums(enums: i)
        }.play()
}
  1. Animate current buttons node opacity to 0.
  2. Update menu items by changing items variable value.
  3. In didSet property observer of items variable animate buttons node if you want.
kier70 commented 5 years ago

I might have to look at another solution as the switching of items removes the onTouchPressed handler. Are there any examples of switching items without any of the guff I require like centralising the menu?

shipinev commented 5 years ago

the switching of items removes the onTouchPressed handler

That's strange because createCustomButton method recreates onTouchPressed event handler.

Are there any examples of switching items without any of the guff I require like centralising the menu?

There are no examples with particular use case you described in this issue, but with a custom scene and Macaw API, you can achieve it.

As far as I see you trying to modify Custom Menu from Examples project, but I think that you need to start from scratch. Also, you should take a look at Macaw API documentation and examples since you want significantly change default FanMenu behavior.

kier70 commented 5 years ago

I think I'll do that.

Thanks for your help