Code Monkey home page Code Monkey logo

Comments (11)

Goktug avatar Goktug commented on May 28, 2024 3

@YuantongL I found a way without too much hacking.

First, you need to hide the UITabBar globally.

UITabBar.appearance().isHidden = true

And then you need to create an environment variable to pass active tab value to inside through subviews

struct ActiveTab: EnvironmentKey {
  static let defaultValue: Int = 0
}

we need to use the customize method of the coordinator to be able to place our custom tab bar and then we need to pass activeTab value to be able to make our custom tab bar fully functional

  @ViewBuilder func customize(_ view: AnyView) -> some View {
    ZStack(alignment: .bottom) {
      view
      CustomTabBarView() // <-- This is our custom tab bar
    }
    .environment(\.activeTab, self.child.activeTab)
  }

Every time user changed the tab, we need to pass the new active tab index value. Therefore, we'll be using tab item creating @ViewBuilder methods

  @ViewBuilder func makeHomeTab(isActive: Bool) -> some View {
      EmptyView()
        .environment(\.activeTab, self.child.activeTab)
  }

Almost everything is done, the only thing that we need to do is, handling the navigation when user click a specific custom tab button

  Button {
    _ = router <-- Injecting coordinator router
      .focusFirst(\.home)
      .child
  } label: {
     // activeTab == 0 <-- You can change the UI with checking the active tab index value
     // Tab Item UI omitted
  }

Finally, I managed to create a custom tab bar using this structure without hacking the library. I hope I would be helpful to you as well

from stinsen.

YuantongL avatar YuantongL commented on May 28, 2024 1

@Goktug Thanks, that's a nice approach, definitely better then the hack!

I made similar change to my project, the only difference is, instead of pass in environment variable, I made it through a binding.

struct CustomTabBarView: View {
    @Binding
    var activeTabIndex: Int
    var body: some View {
        HStack {
            Button {
                activeTabIndex = 0
            } label: {
                Text("Tab 0")
            }
            Button {
                activeTabIndex = 1
            } label: {
                Text("Tab 1")
            }
    }
}

Then in the coordinator, do the following

    private var activeTabIndex = 0 {
        didSet {
            child.activeTab = activeTabIndex
        }
    }

    @ViewBuilder func customize(_ view: AnyView) -> some View {
        ZStack(alignment: .bottom) {
            view
            CustomTabBarView(activeTabIndex: .init(get: {
                self.activeTabIndex
            }, set: { newValue in
                self.activeTabIndex = newValue
            }))
        }
    }

from stinsen.

YuantongL avatar YuantongL commented on May 28, 2024 1

@Goktug my approch doesn't have to depend on tab clicks if I simply remove the private activeTabIndex and use

    @ViewBuilder func customize(_ view: AnyView) -> some View {
        ZStack(alignment: .bottom) {
            view
            CustomTabBarView(activeTabIndex: .init(get: {
                self.child.activeTab
            }, set: { newValue in
                self.child.activeTab = newValue
            }))
        }
    }

Anything that has a TabCoordinator.Router can change tab via focusFirst and our custom tab bar changes accordingly (since we are getting the index value from child.activeTab).
I think both approach works, it is just personally I prefer pass around a Binding in this case instead of environment object.

from stinsen.

YuantongL avatar YuantongL commented on May 28, 2024

+1

@Goktug I personally have a hack to achieve this, Overriding func view() -> AnyView function in TabCoordinatable gives you opportunity to use your own custom tab bar, you can create one using UIKit or use a pure SwiftUI implementation as you like.

To make it all working, this custom tab bar should mimic the TabCoordinatableView, basically follow its init, this is where the hack comes in, I have to use a @testable import Stinsen to access things like coordinator.child.allItems, and its presentables.

Expose these internal variables to public will do it, but a nicer way is - we can pass in our own ViewBuilder for tab bar and their associated views.

from stinsen.

Goktug avatar Goktug commented on May 28, 2024

Thanks for the advice @YuantongL, I was also trying to hack but couldn't reach the active tab index which is reactive. At least, if the library could provide this value we don't need to use @testable hack

from stinsen.

YuantongL avatar YuantongL commented on May 28, 2024

@Goktug I think #43 this is all we need in order to create a customized tab bar and tab view.

from stinsen.

Goktug avatar Goktug commented on May 28, 2024

Your approach only relies on tab clicks, if you want to navigate through tabs via the router, your approach will fail. E.g. deep link. WDYT?

from stinsen.

rundfunk47 avatar rundfunk47 commented on May 28, 2024

Hi! Instead of a TabCoordinatable, a NavigationCoordinatable can also be used with your previous workaround @Goktug. Then you don't need to hide the tabbar globally - and instead of switching the tab you can use the setRoot-function. I could whip up an example if things are still unclear later...

from stinsen.

2jumper3 avatar 2jumper3 commented on May 28, 2024

How

@Goktug my approch doesn't have to depend on tab clicks if I simply remove the private activeTabIndex and use

    @ViewBuilder func customize(_ view: AnyView) -> some View {
        ZStack(alignment: .bottom) {
            view
            CustomTabBarView(activeTabIndex: .init(get: {
                self.child.activeTab
            }, set: { newValue in
                self.child.activeTab = newValue
            }))
        }
    }

Anything that has a TabCoordinator.Router can change tab via focusFirst and our custom tab bar changes accordingly (since we are getting the index value from child.activeTab). I think both approach works, it is just personally I prefer pass around a Binding in this case instead of environment object.

Hi! How you resolve problem if u need to hide tabView? Custom tab view always showing if u use this method )
photo_2023-01-11 11 21 40

from stinsen.

alvin-7 avatar alvin-7 commented on May 28, 2024

How

@Goktug my approch doesn't have to depend on tab clicks if I simply remove the private activeTabIndex and use

    @ViewBuilder func customize(_ view: AnyView) -> some View {
        ZStack(alignment: .bottom) {
            view
            CustomTabBarView(activeTabIndex: .init(get: {
                self.child.activeTab
            }, set: { newValue in
                self.child.activeTab = newValue
            }))
        }
    }

Anything that has a TabCoordinator.Router can change tab via focusFirst and our custom tab bar changes accordingly (since we are getting the index value from child.activeTab). I think both approach works, it is just personally I prefer pass around a Binding in this case instead of environment object.

Hi! How you resolve problem if u need to hide tabView? Custom tab view always showing if u use this method ) photo_2023-01-11 11 21 40

Did you manage to solve this problem later on? If so, how did you do it?

from stinsen.

2jumper3 avatar 2jumper3 commented on May 28, 2024

Did you manage to solve this problem later on? If so, how did you do it?

Hi @alvin-7 ! Yes, remove everything and create TabBar with UIKit :-)

from stinsen.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.