Twitterのホーム画面とスライドメニューを作る
Published by @SoNiceInfo at 6/24/2020
SwiftUIでTwitterのホーム画面とスライドメニューを作る方法を紹介します。gesture
, DragGesture
を利用して右にスワイプするとスライドメニューが表示されるようにします。
スライドメニューの表示・非表示は、横方向のオフセットの値を変更して実装します。
ホーム画面とスライドメニューを作るための要素
ホーム画面を以下の要素に分解して順番に作り方を紹介します。
メニュー(オレンジ枠)
MenuView.swift
タイトルバーとタブ(青枠)
MainView.swift
タイムライン(緑枠)
TimelineView.swift
組み合わせる
ここでスワイプに関する実装をします。
ContentView.swift
メニュー(オレンジ枠) - MenuView.swift
まずはメニューを用意します。ここではViewの表示内容を実装します。
スワイプに関する部分は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()
}
}
タイトルバーとタブ(青枠) - MainView.swift
NavigationView
とTabView
を組み合わせることでタイトルバーとタブを再現します。
タイトルバーのフォントサイズや背景色はSwiftUIでは直接変更できません。
イニシャライザ内でUINavigationBar.appearance()
, UITabBar.appearance()
で変更します。
//
// MainView.swift
//
import SwiftUI
struct MainView: View {
init() {
// タイトルバーのフォントサイズを変更
UINavigationBar.appearance().titleTextAttributes = [.font: UIFont.systemFont(ofSize: 26)]
// タイトルバーの背景色を変更
UINavigationBar.appearance().barTintColor = UIColor.white
// タイトルバーの裏の背景色を変更
UINavigationBar.appearance().backgroundColor = UIColor.white
// タブバーの背景色を変更
UITabBar.appearance().barTintColor = UIColor.white
// タブバーの裏の背景色を変更
UITabBar.appearance().backgroundColor = UIColor.white
}
var body: some View {
TabView {
NavigationView {
ScrollView (.vertical, showsIndicators: false) {
// この部分は次のセクションで実装します。
TimelineView(timelines: timelines)
}
// タイトルと左右のアイコンを指定
.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")
}
}
// 選択されているアイコンの色を青に変更
.accentColor(.blue)
}
}
// Iconの形式をそろえる
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()
}
}
タイムライン(緑枠) - TimelineView.swift
モックはstruct Timeline
で雛形を作りlet timelines: [Timeline]
で定義しています。ForEach
でtimelines
をひとつずつ順番に表示する処理をしています。
//
// 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)
}
}
組み合わせる - ContentView.swift
上記で作成したMenuView
とMainView
をHStack
で表示します。
画面の横方向のoffset
を変更することでスライドメニューを表示・非表示をしています。
横方向のスワイプはvalue.translation.width
で取得しています。
//
// ContentView.swift
//
import SwiftUI
struct ContentView: View {
// xOffset変数で画面の横のオフセットを保持します
@State private var xOffset = CGFloat.zero
@State private var defaultOffset = CGFloat.zero
var body: some View {
// 画面サイズの取得にGeometoryReaderを利用します
GeometryReader { geometry in
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) {
MenuView()
// 横幅は画面サイズの70%にします
.frame(width: geometry.size.width * 0.7)
Divider()
MainView()
// 横幅は画面サイズの100%にします
.frame(width: geometry.size.width)
}
// 最初に画面のオフセットの値をスライドメニュー分マイナスします。
.onAppear(perform: {
self.xOffset = geometry.size.width * -0.7
self.defaultOffset = self.xOffset
})
.offset(x: self.xOffset)
// 画面サイズを明示します
.frame(width: geometry.size.width, alignment: .leading)
// スライドのアニメーションを設定します
.animation(.default)
// ジェスチャーに関するイベントを検知します
.gesture(
// ドラッグ、すなわちスライドやスワイプに関するイベントに関して検知します
DragGesture()
// スワイプが検知されたときの動きを実装します
.onChanged{ value in
// スワイプの移動距離が5以上のときにオフセットの値を動的に変化させます
// しきい値(ここでは5)を超えるとリアルタイムな動きを与えます
if (self.xOffset != .zero && value.translation.width > 5) {
self.xOffset = self.defaultOffset + value.translation.width
}
}
// スワイプが終了したときの動きを実装します
.onEnded { value in
// もし、右方向にスワイプした距離が5以上ならオフセットを0にします
// すなわち、メニューを表示します
// それ以外はオフセットをスライドメニュー分設定します
// すなわちスライドメニューを隠します
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