├── .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 | --------------------------------------------------------------------------------