Create a Twitter Home Screen and Slide Menu
Published by @SoNiceInfo at 6/24/2020
I'll show you how to create a Twitter home screen and slide menu with SwiftUI.
Use gesture
and DragGesture
to swipe to the right to bring up the slide menu. To show or hide the slide menu, change the value of the horizontal offset.
Elements for Creating the Home Screen and Slide Menu
Menu (Orange frame)
MenuView.swift
Title Bar & Tab (Blue frame)
MainView.swift
Timeline (Green frame)
TimelineView.swift
Combine
Implementing swipe here
ContentView.swift
Menu (Orange frame) - MenuView.swift
First, let's prepare the menu. Here, we implement the contents of the View.
The swiping part is implemented in ContentView.swift.
//
// MenuView.swift
//
import SwiftUI
struct MenuView: View {
var body: some View {
VStack(alignment: .leading) {
Image("animal_kuma")
.resizable()
.overlay(
Circle().stroke(Color.gray, lineWidth: 1))
.frame(width: 60, height: 60)
.clipShape(Circle())
Text("SwiftUIへの道")
.font(.largeTitle)
Text("@road2swiftui")
.font(.caption)
Divider()
ScrollView (.vertical, showsIndicators: true) {
HStack {
Image(systemName: "person")
Text("Profile")
}
HStack {
Image(systemName: "list.dash")
Text("Lists")
}
HStack {
Image(systemName: "text.bubble")
Text("Topics")
}
}
Divider()
Text("Settings and privacy")
}
.padding(.horizontal, 20)
}
}
struct MenuView_Previews: PreviewProvider {
static var previews: some View {
MenuView()
}
}
Title Bar & Tab (Blue frame) - MainView.swift
Create Title Bar and Tab with combining NavigationView
and TabView
.
The font size and background color of the title bar cannot be changed directly in the SwiftUI.
Use UINavigationBar.appearance()
and UITabBar.appearance()
in the initializer.
//
// MainView.swift
//
import SwiftUI
struct MainView: View {
init() {
// Change the font size of the title bar.
UINavigationBar.appearance().titleTextAttributes = [.font: UIFont.systemFont(ofSize: 26)]
// Change the background color of the title bar.
UINavigationBar.appearance().barTintColor = UIColor.white
// Change the background color behind the title bar.
UINavigationBar.appearance().backgroundColor = UIColor.white
// Change the background color of the tab bar.
UITabBar.appearance().barTintColor = UIColor.white
// Change the background color behind the tab bar.
UITabBar.appearance().backgroundColor = UIColor.white
}
var body: some View {
TabView {
NavigationView {
ScrollView (.vertical, showsIndicators: false) {
// This part is implemented in the next section.
TimelineView(timelines: timelines)
}
// Specify the title and left and right icons.
.navigationBarTitle(Text("🐥"), displayMode: .inline)
.navigationBarItems(
leading: Image("animal_kuma")
.resizable()
.overlay(
Circle().stroke(Color.gray, lineWidth: 1))
.frame(width: 30, height: 30)
.clipShape(Circle()),
trailing: HStack{
IconView(systemName: "sparkles")
}
.padding(.bottom, 10)
)
}
.tabItem {
IconView(systemName: "house")
}
IconView(systemName: "magnifyingglass")
.tabItem {
IconView(systemName: "magnifyingglass")
}
IconView(systemName: "bell")
.tabItem {
IconView(systemName: "bell")
}
IconView(systemName: "envelope")
.tabItem {
IconView(systemName: "envelope")
}
}
// Change the color of the selected icon to blue.
.accentColor(.blue)
}
}
// Consistent Icon Formats
struct IconView: View {
var systemName: String
var body: some View {
Image(systemName: systemName)
.font(.title)
}
}
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView()
}
}
Timeline (Green frame) - TimelineView.swift
Creating template with struct Timeline
, define mock with let timelines: [Timeline]
.
Use ForEach
to display timelines
one by one.
//
// TimelineView.swift
//
import SwiftUI
struct Timeline {
let id: Int
let name: String
let image: String
let post: String
let post_image: String
}
let timelines: [Timeline] = [
Timeline(id: 0, name: "Arupaka", image: "animal_arupaka", post: "This is post content", post_image: "ice_1"),
Timeline(id: 1, name: "Buta", image: "animal_buta", post: "This is post content", post_image: "ice_2"),
Timeline(id: 2, name: "Hamster", image: "animal_hamster", post: "This is post content", post_image: "flower"),
Timeline(id: 3, name: "Hiyoko", image: "animal_hiyoko", post: "This is post content", post_image: "moon"),
Timeline(id: 4, name: "Inu", image: "animal_inu", post: "This is post content", post_image: "animal_inu")
]
struct TimelineView: View {
let timelines: [Timeline]
var body: some View {
VStack() {
ForEach(self.timelines, id: \.id) { (timeline) in
VStack(spacing: 5) {
HStack(alignment: .top) {
Image(timeline.image)
.resizable()
.clipShape(Circle())
.overlay(
Circle().stroke(Color.white, lineWidth: 4))
.frame(width: 60, height: 60, alignment: .leading)
VStack(alignment: .leading) {
HStack {
Text(timeline.name)
.fontWeight(.bold)
Text("@\(timeline.name)")
.foregroundColor(.gray)
}
Text(timeline.post)
Image(timeline.post_image)
.resizable()
.scaledToFill()
.frame(height: 200)
.cornerRadius(20)
}
}
.padding(.horizontal, 10)
Divider()
}
}
}
}
}
struct TimelineView_Previews: PreviewProvider {
static var previews: some View {
TimelineView(timelines: timelines)
}
}
Combine - ContentView.swift
Display the MenuView
and MainView
with HStack
.
Change offset
in the horizontal direction to show or hide the slide menu.
You can retrive the swipe distance with value.translation.width
.
//
// ContentView.swift
//
import SwiftUI
struct ContentView: View {
// Preserves the horizontal offset of the screen in the xOffset variable.
@State private var xOffset = CGFloat.zero
@State private var defaultOffset = CGFloat.zero
var body: some View {
// Use GeometoryReader to get the screen size
GeometryReader { geometry in
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) {
MenuView()
// Width should be 70% of the screen size
.frame(width: geometry.size.width * 0.7)
Divider()
MainView()
// Width should be 100% of the screen size
.frame(width: geometry.size.width)
}
// First, minus the value of the screen offset by the slide menu.
.onAppear(perform: {
self.xOffset = geometry.size.width * -0.7
self.defaultOffset = self.xOffset
})
.offset(x: self.xOffset)
// Specify the screen size.
.frame(width: geometry.size.width, alignment: .leading)
// Set the animation of the slide.
.animation(.default)
// Detects gesture-related events.
.gesture(
// Detects events related to dragging, i.e., sliding and swiping
DragGesture()
// Implements the movement when a swipe is detected
.onChanged{ value in
// Dynamically changes the offset value when the swipe distance is greater than 5
// Exceeding the threshold (5 in this case) will give you real-time movement
if (self.xOffset != .zero && value.translation.width > 5) {
self.xOffset = self.defaultOffset + value.translation.width
}
}
// Implement the movement when the swipe is finished
.onEnded { value in
// If the swipe distance to the right is greater than 5, set the offset to 0
// i.e. display the menu
// Otherwise, set the offset to the slide menu
// i.e. hide the slide menu
if (value.translation.width > 5) {
self.xOffset = .zero
} else {
self.xOffset = self.defaultOffset
}
}
)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
素材
かわいいフリー素材集 いらすとやBeautiful Free Images & Pictures | Unsplash