├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── HomeView.swift
├── Package.swift
├── README.md
└── Sources
├── Resources
└── Media.xcassets
│ ├── Contents.json
│ └── airplane.imageset
│ ├── Contents.json
│ └── airplane.png
└── SideMenu
├── SideMenu.swift
└── SideMenuView.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/HomeView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeView.swift
3 | // Created by Abdullah Kardas on 20.08.2022.
4 | //
5 |
6 | import SwiftUI
7 | import SideMenu
8 |
9 | struct HomeView: View {
10 | @State var isMenuOpen:Bool = true
11 | let tabs = [MenuTabModel(title: "Home", imageName: "house"),MenuTabModel(title: "Profile", imageName: "person"),MenuTabModel( title: "Settings", imageName: "gear")]
12 |
13 | @State var selectedTab:MenuTabModel = MenuTabModel(title: "Home", imageName: "house")
14 | @State var backColor = Color.white
15 |
16 |
17 | var body: some View {
18 | ZStack {
19 | SideMenuView(
20 | isMenuOpen: $isMenuOpen,
21 | tabs: tabs, // add your [MenuTabModel]
22 | selectedTab: $selectedTab, //initial selectedTab
23 | backColor: $backColor, //acces view placeholder background
24 | backImage: "airplane", //add your background image! Default value is airplane
25 | selectionColor: .purple,
26 | blurRadius: 32, // add blur radius for image default value is 32
27 | enable3D: true //enable/disable 3D effect
28 | ) {
29 | if selectedTab.title == "Home" {
30 | TabOne(isMenuOpen: $isMenuOpen, backColor: $backColor)
31 | }else if selectedTab.title == "Profile" {
32 | TabTwo(isMenuOpen: $isMenuOpen, backColor: $backColor)
33 | }else if selectedTab.title == "Settings" {
34 | TabThree(isMenuOpen: $isMenuOpen, backColor: $backColor)
35 | }
36 | }
37 | }
38 |
39 | }
40 | }
41 |
42 | struct HomeView_Previews: PreviewProvider {
43 | static var previews: some View {
44 | HomeView()
45 | }
46 | }
47 |
48 | struct TabOne: View {
49 | @Binding var isMenuOpen:Bool
50 | @Binding var backColor:Color
51 | var body: some View {
52 | ZStack {//Your content is here
53 | backColor.ignoresSafeArea().cornerRadius(isMenuOpen ? 12:0)
54 | VStack(alignment:.leading) {
55 | Button(action: { isMenuOpen.toggle() }) {
56 | Image(systemName: "line.3.horizontal").font(.title).foregroundColor(.white)
57 |
58 | }.padding(.top, 12).padding(.leading, 12).frame(maxWidth: .infinity,alignment: .leading)
59 | Spacer()
60 | }
61 | Spacer()
62 | Text("Tab 1").foregroundColor(.white).font(.title2).onTapGesture {
63 | print("something")
64 | }
65 | }.onAppear{
66 | backColor = .pink
67 | }
68 | }
69 | }
70 | struct TabTwo: View {
71 | @Binding var isMenuOpen:Bool
72 | @Binding var backColor:Color
73 | var body: some View {
74 | ZStack {//Your content is here
75 | backColor.ignoresSafeArea().cornerRadius(isMenuOpen ? 12:0)
76 | VStack(alignment:.leading) {
77 | Button(action: { isMenuOpen.toggle() }) {
78 | Image(systemName: "line.3.horizontal").font(.title).foregroundColor(.white)
79 |
80 | }.padding(.top, 12).padding(.leading, 12).frame(maxWidth: .infinity,alignment: .leading)
81 | Spacer()
82 | }
83 | Spacer()
84 | Text("Tab 2").foregroundColor(.white).font(.title2).onTapGesture {
85 | print("something")
86 | }
87 | }.onAppear {
88 | backColor = .green
89 | }
90 | }
91 | }
92 | struct TabThree: View {
93 | @Binding var isMenuOpen:Bool
94 | @Binding var backColor:Color
95 | var body: some View {
96 | ZStack {//Your content is here
97 | backColor.ignoresSafeArea().cornerRadius(isMenuOpen ? 12:0)
98 | VStack(alignment:.leading) {
99 | Button(action: { isMenuOpen.toggle() }) {
100 | Image(systemName: "line.3.horizontal").font(.title).foregroundColor(.white)
101 |
102 | }.padding(.top, 12).padding(.leading, 12).frame(maxWidth: .infinity,alignment: .leading)
103 | Spacer()
104 | }
105 | Spacer()
106 | Text("Tab 3").foregroundColor(.white).font(.title2).onTapGesture {
107 | print("something")
108 | }
109 | }.onAppear {
110 | backColor = .purple
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.7
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "SideMenu",
8 | products: [
9 | // Products define the executables and libraries a package produces, and make them visible to other packages.
10 | .library(
11 | name: "SideMenu",
12 | targets: ["SideMenu"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
21 | .target(
22 | name: "SideMenu",
23 | dependencies: [],
24 | path: "Sources", resources: [
25 | .process("Resources/Media.xcassets")
26 | ]),
27 |
28 |
29 | ]
30 | )
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SideMenu - SwiftUI
2 |
3 | ## Install
4 |
5 | ### Swift Package Manager
6 |
7 | Open `Xcode`, go to `File -> Swift Packages -> Add Package Dependency` and enter `https://github.com/akardas16/SideMenu.git` as Branch `main`
8 |
9 | You need to add `import SideMenu`
10 |
11 | ## Usage
12 | Without 3D effect
13 | With 3D effect
14 |
15 | * initilize `SideMenuView` with available parameters
16 |
17 |
18 | ```Swift
19 | SideMenuView(
20 | isMenuOpen: $isMenuOpen,
21 | tabs: tabs, // add your [MenuTabModel]
22 | selectedTab: $selectedTab, //initial selectedTab
23 | backColor: $backColor, //acces view placeholder background
24 | backImage: "airplane", //add your background image! Default value is airplane
25 | selectionColor: .purple,
26 | blurRadius: 32, // add blur radius for image default value is 32
27 | enable3D: true //enable/disable 3D effect
28 | ) {
29 | if selectedTab.title == "Home" {
30 | TabOne(isMenuOpen: $isMenuOpen, backColor: $backColor)
31 | }else if selectedTab.title == "Profile" {
32 | TabTwo(isMenuOpen: $isMenuOpen, backColor: $backColor)
33 | }else if selectedTab.title == "Settings" {
34 | TabThree(isMenuOpen: $isMenuOpen, backColor: $backColor)
35 | }
36 | }
37 | ```
38 |
39 | * Example tabs array
40 |
41 | ```Swift
42 | let tabs = [MenuTabModel(title: "Home", imageName: "house"),MenuTabModel(title: "Profile", imageName: "person"),MenuTabModel( title: "Settings", imageName: "gear")]
43 | ```
44 |
45 | ```Swift
46 | @State var isMenuOpen:Bool = true
47 | @State var selectedTab:MenuTabModel = MenuTabModel(title: "Home", imageName: "house")
48 | @State var backColor = Color.pink
49 | ```
50 | ### Want to try library quickly?
51 | * Paste `HomeView.swift` file to your project and see UI on preview
52 |
--------------------------------------------------------------------------------
/Sources/Resources/Media.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/Resources/Media.xcassets/airplane.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "airplane.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/Resources/Media.xcassets/airplane.imageset/airplane.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akardas16/SideMenu/97a716df8f721a18bc98f8afeddb36ae2a0efabb/Sources/Resources/Media.xcassets/airplane.imageset/airplane.png
--------------------------------------------------------------------------------
/Sources/SideMenu/SideMenu.swift:
--------------------------------------------------------------------------------
1 | public struct SideMenu {
2 | public private(set) var text = "Hello, World!"
3 |
4 | public init() {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/SideMenu/SideMenuView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | @available(iOS 14.0, *)
4 | public struct SideMenuView: View {
5 | @Binding private var isMenuOpen: Bool
6 | public var tabs:[MenuTabModel]
7 | @Binding var selectedTab:MenuTabModel
8 | @Binding var backColor:Color
9 | public var backImage:String = "airplane"
10 | public var selectionColor:Color = Color.blue
11 | public var blurRadius:CGFloat = 32
12 | public var enable3D:Bool = true
13 | public let content:Content
14 |
15 |
16 | public init(isMenuOpen:Binding,tabs:[MenuTabModel] ,selectedTab:Binding,backColor:Binding,backImage:String = "airplane",selectionColor:Color = Color.blue,blurRadius:CGFloat = 32,enable3D:Bool = true, @ViewBuilder content: () -> Content) {
17 | self.tabs = tabs
18 | self.blurRadius = blurRadius
19 | self.enable3D = enable3D
20 | self.selectionColor = selectionColor
21 | self.backImage = backImage
22 | _isMenuOpen = isMenuOpen
23 | _selectedTab = selectedTab
24 | self.content = content()
25 | _selectedTab = selectedTab
26 | _backColor = backColor
27 | }
28 |
29 | public var body: some View {
30 | ZStack {
31 | menuBackgroundView
32 | ZStack {
33 | RoundedRectangle(cornerRadius: isMenuOpen ? 12 : 0)
34 | .foregroundColor(backColor)
35 | .shadow(color: .black.opacity(0.6), radius: isMenuOpen ? 14 : 0)
36 |
37 | content
38 | .disabled(isMenuOpen)
39 | .padding(.top,isMenuOpen ? 0: window()?.safeAreaInsets.top)
40 | .padding(.bottom,isMenuOpen ? 0: window()?.safeAreaInsets.bottom)
41 | }
42 | .offset(x: isMenuOpen ? (window()?.bounds.width ?? UIScreen.main.bounds.width) * 0.5 : 0)
43 | .scaleEffect(isMenuOpen ? 0.8 : 1)
44 | .rotation3DEffect(.degrees(isMenuOpen && enable3D ? -32:0), axis: (x: 0, y: 1, z: 0))
45 | .animation(.linear(duration: 0.24), value: isMenuOpen)
46 | .ignoresSafeArea(edges: isMenuOpen ? []:[.all])
47 | .onTapGesture {
48 | if isMenuOpen {
49 | isMenuOpen.toggle()
50 | }
51 | }
52 |
53 | }
54 | }
55 |
56 | public var menuBackgroundView:some View {
57 | ZStack(alignment:.init(horizontal: .leading, vertical: .center)){
58 | Color.pink.ignoresSafeArea().overlay(
59 | Image(uiImage: UIImage(named: backImage, in: .module, with: nil)!).scaledToFill().ignoresSafeArea().blur(radius: blurRadius)
60 | )
61 |
62 | VStack(alignment:.leading) {
63 | ForEach(tabs) { tab in
64 | HStack {
65 | Image(systemName: tab.imageName)
66 | Text(tab.title)
67 | }.font(.headline)
68 | .foregroundColor(.white)
69 | .padding()
70 | .padding(.horizontal,4)
71 | .background(
72 | selectedTab.title == tab.title ? Capsule(style: .circular).fill(selectionColor) : Capsule(style: .circular).fill(Color.clear)
73 |
74 | ).onTapGesture {
75 | selectedTab = tab
76 | closeMenuWithDly()
77 | }
78 | .animation(.linear(duration: 0.24), value: selectedTab)
79 | }
80 | }.padding()
81 | }
82 |
83 | }
84 |
85 | func closeMenuWithDly(){
86 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
87 | isMenuOpen.toggle()
88 | })
89 | }
90 | func window() -> UIWindow?{
91 | guard let scene = UIApplication.shared.connectedScenes.first,
92 | let windowSceneDelegate = scene.delegate as? UIWindowSceneDelegate,
93 | let window = windowSceneDelegate.window else {
94 | return nil
95 | }
96 | return window
97 | }
98 | }
99 |
100 |
101 |
102 |
103 | public struct MenuTabModel:Identifiable,Equatable {
104 |
105 | public var id: UUID = UUID()
106 | public var title:String
107 | public var imageName:String
108 | public init(id: UUID = UUID(), title: String, imageName: String) {
109 | self.id = id
110 | self.title = title
111 | self.imageName = imageName
112 | }
113 | }
114 |
--------------------------------------------------------------------------------