前回は、SwiftUIで、NavigationStack を利用してナビゲーション画面遷移を行う
アプリケーションを開発する記事を紹介しました。
今回は、SwiftUIで、写真などの画像をスワイプ(スライド)させて移動させたり、
画像をピンチイン・ピンチアウト(縮小・拡大)するアプリケーションを開発する記事を
紹介しようと思います。
習得内容
今回、SwiftUIで 写真などの画像や画面をスワイプ(スライド)させて移動させたり、
ピンチイン・アウト(縮小・拡大)するアプリケーションを作成する事で、下記の内容を
習得する事が出来ます。
- TabView:複数の子Veiwを切り替えることで、画像や画面をスワイプを実現します。
- NavigationStack:3種類の画面遷移から、配列コントロールした遷移を実現します。
- MagnificationGesture:iOS16までピンチイン・ピンチアウトを実現していたジェスチャーですが、ズームのアンカーポイント(中心軸)が取得出来なく、左上に固定されてしまう問題が有りました。iOS17以降は非推奨となり、代わりに MagnifyGesture が公開されています。
- MagnifyGesture:iOS17以降でピンチイン・ピンチアウトを実現するジェスチャーで、ズームのアンカーポイント(中心軸)を取得することが出来るようになりました。
- Assets:Xcodeで、画像や動画、音声等のコンテンツファイルを格納・管理するフォルダで、利用方法を習得します。
動作環境
今回も、アプリが動作する環境は下記になります。
- mac OS:Sequoia 15.6.1
- iOS:26.0
- Xcode:26.0.1
- Swift:6.2.1
NavigationStack
NavigationStack について、前回の記事で詳しく説明しましたが、画面遷移について、
簡単におさらいしておきます。
NavigationStack:3種類の画面遷移
NavigationStack には、下記の3種類の画面遷移が用意されています。
- 直接遷移:リンクやボタンを押すことで、直接、画面遷移を実現します。(NavigationStack+NavigationLink)
- 遷移コントロール:リンクやボタンを押すことで、押したリンクの結果を受けて、画面遷移を一箇所でコントロールします。(NavigationStack+NavigationLink+navigationDestination)
- 配列コントロール:画面の遷移状況を管理する配列(path)を用意し、配列操作の結果を受けて、画面遷移を一箇所でコントロールします。(NavigationStack(path: $path)+navigationDestination)
今回の記事では、上記から、3:配列コントロールを実現します。
NavigationStack による、3種類の画面遷移の詳細は、下記を参考にしています。
NavigationStack → SwipeView → SwipeDetailView
では、NavigationStack で、配列コントロールを実現する為に、少し階層を増やして
下記の2つのViewを作成して行きます。
- SwipeView:表示用のViewで、画像のピンチイン・ピンチアウト(縮小・拡大)を実現します。
- SwipeDetailView:詳細用のViewで、画像のスワイプ(スライド)を実現します。
Xcode の上部タブメニューより、File → New → File from Template を選択します。
SwiftUI View を選択します。
名称は、SwipeView を入力してみます。
Create ボタンを押して、SwipeView.swift を作成します。
続けて、Xcode の上部タブメニューより、File → New → File from Template を選択します。
SwiftUI View を選択します。
名称は、SwipeDetailView を入力してみます。
Create ボタンを押して、SwipeDetailView.swift を作成します。
ContentView:メインメニュー(ランチャー)
前回の記事で作成したメインメニュー(ランチャー):ContentView を確認します。
import SwiftUI
struct ContentView: View {
@State var isPopupView_1 = false
@State var isPopupView_2 = false
@State var isPopupView_3 = false
@State var isPopupView_4 = false
@State var isPopupView_5 = false
var body: some View {
VStack{
Button {
self.isPopupView_1.toggle()
} label: {
Text("QRCodeView")
.font(.title2)
}
.fullScreenCover(isPresented: $isPopupView_1, content: {
QRCodeView(isPopupView_1: $isPopupView_1)
})
.padding()
Button {
self.isPopupView_2.toggle()
} label: {
Text("ColorPickerView")
.font(.title2)
}
.sheet(isPresented: $isPopupView_2, content: {
ColorPickerView(isPopupView_2: $isPopupView_2)
})
.padding()
Button {
self.isPopupView_3.toggle()
} label: {
Text("ColorSliderRGBView")
.font(.title2)
}
.sheet(isPresented: $isPopupView_3, content: {
ColorSliderRGBView(isPopupView_3: $isPopupView_3)
})
.padding()
Button {
self.isPopupView_4.toggle()
} label: {
Text("ColorSliderHSBView")
.font(.title2)
}
.sheet(isPresented: $isPopupView_4, content: {
ColorSliderHSBView(isPopupView_4: $isPopupView_4)
})
.padding()
Button {
self.isPopupView_5.toggle()
} label: {
Text("NavigationListView")
.font(.title2)
}
.sheet(isPresented: $isPopupView_5, content: {
NavigationListView(isPopupView_5: $isPopupView_5)
})
.padding()
}
}
}
#Preview {
ContentView()
}
メインメニュー(ランチャー)は、以下の内容です。
- QRCodeView
- ColorPickerView
- ColorSliderRGBView
- ColorSliderHSBView
- NavigationListView
今回は、メインメニュー(ランチャー):ContentView に変更は有りませんので、
このまま利用して行きます。
NavigationListView:配列コントロール
NavigationListVeiw のソースに、追記・修正をして行きます。
ソース例:NavigationListVeiw(遷移コントロール)
先ずは、前回の記事で、遷移コントロールとして作成した NavigationListVeiw の
ソースを確認してみます。
import SwiftUI
struct NavigationListView: View {
@Binding var isPopupView_5: Bool
var navi_lists = [
"SwipeView",
"CarouselView",
"TabVeiw",
"TabBarView"
]
var body: some View {
VStack {
NavigationStack {
List(navi_lists, id: \.self) { navi_list in
NavigationLink(navi_list, value: navi_list)
.font(.system(size: 22))
}
.navigationDestination(for: String.self) { navi_list in
switch (navi_list) {
case "SwipeView":
Text(navi_list)
.font(.system(size: 22))
.foregroundColor(.blue)
.padding()
case "CarouselView":
Text(navi_list)
.font(.system(size: 22))
.foregroundColor(.pink)
.padding()
case "TabVeiw":
Text(navi_list)
.font(.system(size: 22))
.foregroundColor(.green)
.padding()
case "TabBarView":
Text(navi_list)
.font(.system(size: 22))
.foregroundColor(.orange)
.padding()
default:
Text(navi_list)
.font(.system(size: 22))
.foregroundColor(.blue)
.padding()
}
}
.navigationTitle("NavigationList")
.font(.system(size: 22))
.foregroundColor(.purple)
}
}
Button(action: {
withAnimation {
self.isPopupView_5.toggle()
}
}, label: {
Text("Close")
.font(.title2)
.foregroundColor(.black)
})
.padding()
}
}
#Preview {
NavigationListView(isPopupView_5: .constant(false))
}
ソース例:NavigationListVeiw(配列コントロール)
配列コントロール向けとして NavigationListVeiw のソースに、追記・修正をした
ソースを紹介します。
import SwiftUI
struct NavigationListView: View {
@Binding var isPopupView_5: Bool
@State var path: [String]=[]
var navi_lists = [
"SwipeView",
"CarouselView",
"TabVeiw",
"TabBarView"
]
var body: some View {
VStack {
NavigationStack(path: $path) {
List(navi_lists, id: \.self) { navi_list in
NavigationLink(navi_list, value: navi_list)
.font(.system(size: 22))
}
.navigationDestination(for: String.self) { navi_list in
switch (navi_list) {
case "SwipeView":
SwipeView(path: $path)
.navigationTitle(navi_list)
.navigationBarTitleDisplayMode(.inline)
case "SwipeDetailView":
SwipeDetailView(path: $path)
.navigationTitle(navi_list)
.navigationBarTitleDisplayMode(.inline)
case "CarouselView":
Text(navi_list)
.font(.system(size: 22))
.foregroundColor(.pink)
.padding()
case "TabVeiw":
Text(navi_list)
.font(.system(size: 22))
.foregroundColor(.green)
.padding()
case "TabBarView":
Text(navi_list)
.font(.system(size: 22))
.foregroundColor(.orange)
.padding()
default:
Text(navi_list)
.font(.system(size: 22))
.foregroundColor(.blue)
.padding()
}
}
.navigationTitle("NavigationList")
.font(.system(size: 22))
.foregroundColor(.purple)
}
}
Button(action: {
withAnimation {
self.isPopupView_5.toggle()
}
}, label: {
Text("Close")
.font(.title2)
.foregroundColor(.black)
})
.padding()
}
}
#Preview {
NavigationListView(isPopupView_5: .constant(false))
}
追記・修正をした部分を説明します。
- @State var path: [String]=[]:path の宣言をしています。path でスタック(Stack) を実現します。
- NavigationStack(path: $path) {:NavigationStack に path の引数を渡して、ナビゲーション遷移を実現します。
- SwipeView(path: $path):SwipeView に、 path の引数を渡しています。
- .navigationTitle(navi_list):遷移先のタイトルに、navi_list の内容を設定しています。
- .navigationBarTitleDisplayMode(.inline):遷移先のタイトルを、小さめ(.inline)に設定しています。inline 以外に、大きめ(.large:デフォルト)と自動(.automatic)の設定が出来ます。
- case “SwipeDetailView”:SwipeDetailView を選択された場合の処理を、新たに追加しています。
- SwipeDetailView(path: $path):SwipeDetailView に、path の引数を渡しています。
SwipeView → SwipeDetailView
NavigationStack で、配列コントロールを実現する為に、最初に作成した SwipeView と
SwipeDetailView のソースを紹介して行きますが、事前に、2つのViewで利用する
画像(写真)を Xcode に追加して行きます。
写真などの画像を利用する際は、著作権や肖像権の問題が発生する場合が有りますので、
取り扱いには、十分にご注意下さい。
以前、Python でスクレイピングを紹介する記事でも、写真などの画像を利用する際の
注意事項について記載していますので、参考にして頂ければと思います。
画像(写真)の追加
著作権や肖像権を考慮して、今回は、筆者の家で飼っている猫の写真を利用します。
Xcode で、ソースの左欄に見える Navigator Area(ナビゲーター・エリア)を確認すると、
画像を格納・管理するフォルダ:Assets(下記の赤枠)が有ると思います。
Assets をクリックすると、Navigator Area(ナビゲーター・エリア)の右側が開き、
Assets の中身を確認する事が出来ます。
Finder から、好きな猫の写真をドラッグ&ドロップします。
画像(写真)は、著作権や肖像権の問題が無いものを、各環境に合わせて、投入して下さい。
今回は、6枚の猫の写真をドラッグ&ドロップしてみました。
もし、画像(写真)の名称をそのまま利用したく無い場合は、Assets 内で名称を
変更する事も出来ますので、試してみて下さい。
これで、SwiftUIのソースから直接、画像(写真)を呼び出して利用する事が出来ます。
ソース例:SwipeView
NavigationStack から選択して、表示用のViewで、画像のピンチイン・ピンチアウト
(縮小・拡大)を実現する SwipeView のソースを紹介します。
import SwiftUI
struct SwipeView: View {
@Binding var path: [String]
@State private var currentZoom: CGFloat = 0.0
@State private var totalZoom: CGFloat = 1.0
var body: some View {
ZStack {
Color.white
.edgesIgnoringSafeArea(.all)
VStack {
Image("IMG_0230")
.resizable()
.scaledToFit()
.scaleEffect(totalZoom + currentZoom)
.gesture(
MagnifyGesture() // pinch in/out
.onChanged { value in
currentZoom = value.magnification - 1
}
.onEnded { value in
totalZoom += currentZoom
currentZoom = 0
}
)
.padding()
Button(action: {
withAnimation {
path.append("SwipeDetailView")
}
}, label: {
Text("SwipeDetailView")
.font(.system(size: 22))
.foregroundColor(.green)
})
}
}
}
}
#Preview {
SwipeView(path: .constant(["SwipeView"]))
}
- @State private var currentZoom = 0.0:ズームの変化量を宣言しています。
- @State private var totalZoom = 1.0:ズームの確定値を宣言しています。
- .scaleEffect(totalZoom + currentZoom):scaleEffect にズームした gesture をバインドすることで、自動的にズームを実現します。1.0はオリジナルのサイズです。2.0で2倍、3.0で3倍の拡大サイズです。逆に、1.0未満の0.5は1/2倍、0.25は1/4倍の縮小サイズになります。
- .gesture(:gesture のアクションを記載します。
- MagnifyGesture():MagnifyGesture を呼び出しています。
- .onChanged { value in:gesture が、変化した時に実行するアクションを記載します。
- currentZoom = value.magnification – 1:gesture が、変化した値: value.magnification(倍率)を currentZoom に代入しています。gesture が開始された位置を1として、そこからどれだけ変化しているかを表しているので、変化量を算出する為に -1 を入れています。
- .onEnded { value in:gesture が、終わった時に実行するアクションを記載します。
- totalZoom += currentZoom:totalZoom に、currentZoom の変化量を加えています。
- currentZoom = 0:currentZoom に0を代入して、初期化しています。
- path.append(“SwipeDetailView”):path に、“SwipeDetailView” を追加しています。
MagnifyGesture
MagnifyGesture() は、アンカーポイント(中心軸)を取得して、scaleEffect() へ
ジェスチャーを渡す事により、ジェスチャー位置を中心にズームを実現します。
この為、MagnificationGesture() の時より、ピンチイン・ピンチアウト(縮小・拡大)の
実装が簡単になっています。
MagnifyGesture() については、下記のサイトを参考にしています。
ソース例:SwipeDetailView
SwipeView から呼び出される詳細用のViewで、画像のスワイプ(スライド)を
実現する SwipeDetailView のソースを紹介します。
import SwiftUI
struct SwipeDetailView: View {
@Binding var path: [String]
@GestureState private var zoom: CGFloat = 1.0
var imageNames = [
"IMG_0057",
"IMG_0230",
"IMG_0405",
"IMG_0402",
"IMG_0411",
"IMG_0412",
]
var body: some View {
ZStack {
Color.white
.edgesIgnoringSafeArea(.all)
VStack {
TabView {
ForEach(imageNames, id: \.self) { imageName in
withAnimation {
VStack {
Text(imageName)
.foregroundColor(.blue)
.font(.system(size: 20))
Image(imageName)
.resizable()
.scaledToFit()
.scaleEffect(zoom)
.gesture(
MagnifyGesture()
.updating($zoom) { value, gestureState, transaction in
gestureState = value.magnification
}
)
.padding()
}
}
}
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.padding()
Button(action: {
withAnimation {
path.removeAll()
}
}, label: {
Text("NavigationListView")
.font(.system(size: 22))
.foregroundColor(.red)
})
}
}
}
}
#Preview {
SwipeDetailView(path: .constant(["SwipeDetailView"]))
}
- @GestureState private var zoom = 1.0:ズームの変化量を宣言しています。@GestureState は、gesture が実行している間だけ true となり、終了すると自動的に false となります。今回は、ピンチイン・ピンチアウト(縮小・拡大)を行っている間だけ、画像(写真)をズームして、指を離すと自動的に false となります。
- var imageNames = [“IMG_0057″,”IMG_0230”,…]:Assets に保存した画像(写真)の名称を利用する為に、配列で宣言しています。
- TabView {:スワイプ(スライド)を実現する為に、TabView を呼び出しています。
- ForEach(imageNames, id: \.self) { imageName in:Assets に保存した画像(写真)を、ForEach() で配列の画像(写真)を繰り返し生成して表示します。
- Image(imageName):画像(写真)の名称を利用して、表示します。
- .scaleEffect(zoom):scaleEffect にズームした gesture をバインドすることで、自動的にズームを実現します。1.0はオリジナルのサイズです。2.0で2倍、3.0で3倍の拡大サイズです。逆に、1.0未満の0.5は1/2倍、0.25は1/4倍の縮小サイズになります。
- .gesture(:gesture のアクションを記載します。
- MagnifyGesture():MagnifyGesture を呼び出しています。
- .updating($zoom) { value, gestureState, transaction in:gesture が、変化した時に実行するアクションを記載します。.onChanged とは異なり、変化した値は保持しませんので、指を離すとオリジナルのサイズに戻ります。
- gestureState = value.magnification:gesture が、変化した値: value.magnification(倍率)を gestureState に代入しています。
- .tabViewStyle(PageTabViewStyle()):tabViewStyle に PageTabViewStyle() を指定することで、画像の下に点々「…」とページ全体と現在地を表示する事が出来ます。
- .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always)):画像の下に点々「…」の背景を角丸半透明に設定します。
- path.removeAll():path の配列をクリアします。これにより、path が設定されていない初期画面:今回は、NavigationListVeiw に戻ります。もし、ひとつ前の画面に戻りたい場合は、画面の左上の「<」ボタンを押す(タップ)するか、path.remove(at: 1) のように、View自身の配列のインデックス番号を指定して削除処理を実行します。
TabVeiw
TabView とは、複数の 子View を切り替えることのできる View です。
SwiftUI の Veiw は、アプリの画面に表示されるものを指すので、画面や画像だけではなく、
ボタンや文字(テキスト)なども切り替えて、表示することが出来ます。
表示方法は、TabView {} の中に、View を入れる形になります。
上記のソースのように、TabView { Text(”…”) Image(”…”) } の要領です。
Tabですが、最大5つまで表示させる事が出来ます。Tabが6つ以上になると、More という
リストのTabになり、5つ目以降のTabが格納されます。
アプリ実行:シミュレータ起動
では、実際にアプリを実行してみます。
Xcode の上部タブメニューより、Product → Run を選択するか、command + R を
押してみて下さい。
エラーが無ければ、iPhoneシミュレータが起動します。
ContntVeiw(メインメニュー)が表示されました。
NavigationListView:ナビゲーション画面
続けて、NavigationListViewボタンを押してみます(タップします)。
ContntVeiw(メインメニュー)の上に NavigationListVeiw(ナビゲーション機能)が
重なって、表示されたました。
NavigationList の内容は、下記の通りで変更はありません。
- SwipeView
- CarouselView
- TabVeiw
- TabBarView
現在の 配列(path) の状態は、下記になります。
- path = []
SwipeView:ピンチイン・ピンチアウト
次に、NavigationList の上段の SwipeView を押してみます(タップします)。
前回の SwipeView のテキストの表示と異なり、2匹の猫の画像が表示されました。
画面上部には、SwipeView のタイトルが表示されています。
NavigationListVeiwで、タイトルを表示するように設定していましたね。
猫の画像の下には、SwipeDetailView のボタンが表示されています。
現在の 配列(path) の状態は、下記になります。
- path = [“SwipeView”]
では、画像のピンチイン・ピンチアウト(縮小・拡大)を行ってみます。
iPhoneシミュレータで、画像のピンチイン・ピンチアウト(縮小・拡大)を実現するには、
画像の上にカーソルを移動させた後、optionキーを押した状態で、トラックパッドに
2本の指で触れてみて下さい。
上記のように、画像の上に●印が2つ表示された事が確認出来れば、ピンチイン・ピンチアウト
(縮小・拡大)の操作が出来るようになります。
ピンチイン(縮小)を実現するには、optionキーを押した状態で、トラックパッドに
2本の指で挟むように動作をしてみて下さい。
トラックパッドから、2本の指を離してみると、上記のように猫の画像が縮小している事が
確認出来ると思います。
もう少し、ピンチイン(縮小)を実現してみます。
同じうように、optionキーを押した状態で、トラックパッドに2本の指で挟むように
動作をしてみて下さい。
トラックパッドから、2本の指を離してみると、上記のように猫の画像がかなり縮小
している事が確認出来ると思います。
今度は、ピンチアウト(拡大)を実現してみます。
同じうように、optionキーを押した状態で、トラックパッドに2本の指で広げるように
動作をしてみて下さい。
トラックパッドから、2本の指を離してみると、上記のように猫の画像が拡大している事が
確認出来ると思います。
SwipeDetailView:スワイプ(スライド)
ピンチイン・ピンチアウト(縮小・拡大)の操作が出来るようになりましたので、
今度は、画像のスワイプ(スライド)を実現してみます。
SwipeView で、猫の画像の下には、SwipeDetailView のボタンが表示されていますので、
押して(タップして)みて下さい。
上記のように、SwipeDetailView へ画面推移した事が確認出来ると思います。
画面上部には、SwipeDetailView のタイトルが表示されています。
こちらも、NavigationListVeiw で、タイトルを表示するように設定していました。
猫の画像の上には、画像の名称をテキストで表示しています。
猫の画像の下には、NavigationListVeiw のボタンが表示されています。
現在の 配列(path) の状態は、下記になります。
- path = [“SwipeView”, “SwipeDetailView”]
猫の画像の直下には、点々「…」(緑枠)が表示されていて、Assets で設定した
6枚の猫の写真を、スワイプ(スライド)する事が出来ます。
白〇印は、スワイプ(スライド)の現在地を示しています。
では、iPhoneシミュレータで、画像のスワイプ(スライド)を実現してみます。
トラックパッドの上で、指を右から左へ動かしてみて下さい。
スマホで、スワイプ(スライド)を行う動作と同じです。
画像のスワイプ(スライド)が実現したら、異なる画像の表示が確認出来ると思います。
2枚目の画像は、SwipeView で表示した猫の画像と同じです。
猫の画像の直下には、点々「…」が表示されていて、白〇印の現在地は、
左から2枚目の画像である事が確認出来ます。
続いて、画像のスワイプ(スライド)を実現してみます。
トラックパッドの上で、指を右から左へ動かしてみて下さい。
猫の画像の直下には、点々「…」が表示されていて、白〇印の現在地は、
左から3枚目の画像である事が確認出来ます。
更に、トラックパッドの上で、指を右から左へ動かしてみて下さい。
猫の画像の直下には、点々「…」が表示されていて、白〇印の現在地は、
左から4枚目の画像である事が確認出来ます。
また、トラックパッドの上で、指を右から左へ動かしてみて下さい。
猫の画像の直下には、点々「…」が表示されていて、白〇印の現在地は、
左から5枚目の画像である事が確認出来ます。
最後になりますが、トラックパッドの上で、指を右から左へ動かしてみて下さい。
猫の画像の直下には、点々「…」が表示されていて、白〇印の現在地は、
左から6枚目(右端)の画像である事が確認出来ます。
iPhoneシミュレータで、画像のスワイプ(スライド)の操作が出来るようになりました。
トラックパッドの上で、今度は指を左から右へ(上記とは反対方向)動かしてみると、
画像が戻って表示される事も確認が出来ると思います。
SwipeDetailView:ピンチイン・ピンチアウト
SwipeDetailView のソースの説明でも記載していますが、こちらでも猫の画像に
ピンチイン・ピンチアウト(縮小・拡大)の操作が出来るように、MagnifyGesture()
を実装しています。
但し、SwipeView と異なり、トラックパッドの上で、ピンチイン・ピンチアウト
(縮小・拡大)の操作をした後、指を離すと元の大きさに戻るように設定しています。
NavigationListVeiw ← SwipeDetailView
最後に、SwipeDetailView から、NavigationListVeiw に戻る事を確認してみます。
猫の画像の下には、NavigationListVeiw のボタンが表示されていますので、
押して(タップして)みて下さい。
配列(path) の状態は、下記になります。
- path = [“SwipeView”, “SwipeDetailView”] → path = []
配列(path)が初期状態となり、SwipeView を飛ばして、NavigationListVeiw に
一気に戻る事が確認出来ると思います。
遷移コントロール
NavigationStack には、下記の3種類の画面遷移が用意されていましたね。
- 直接遷移:リンクやボタンを押すことで、直接、画面遷移を実現します。(NavigationStack+NavigationLink)
- 遷移コントロール:リンクやボタンを押すことで、押したリンクの結果を受けて、画面遷移を一箇所でコントロールします。(NavigationStack+NavigationLink+navigationDestination)
- 配列コントロール:画面の遷移状況を管理する配列(path)を用意し、配列操作の結果を受けて、画面遷移を一箇所でコントロールします。(NavigationStack(path: $path)+navigationDestination)
今回の記事では、上記から、3:配列コントロールを実現する為に、path を利用して、
NavigationStack(path) → SwipeView(path) → SwipeDetailView(path)
と配列の結果を受けて、画面遷移を一箇所でコントロールしました。
まとめ
今回は、SwiftUIで、写真などの画像をスワイプ(スライド)させて移動させたり、
画像をピンチイン・ピンチアウト(縮小・拡大)するアプリケーションを開発する
記事を紹介しました。
次回は、SwiftUIで、TabViewについて深堀りしたアプリケーションを開発する
記事を紹介しようと思います。
- イラスト:いらすとや より引用






























コメント