本文是SwiftUI基礎教程基礎教程的第二部分,第一部分參見SwiftUI基礎教程(1)。
第二章:SwiftUI基礎元素實例
2.11 導航
// 以下代碼效果圖如,navi所示
struct SwiftUINavi: View {
var tankInfo = [
ANewTankInfo(name: "M1A2", image: "1"),
ANewTankInfo(name: "T90", image: "2"),
ANewTankInfo(name: "T72", image: "3"),
ANewTankInfo(name: "99A", image: "4"),
ANewTankInfo(name: "LieKeLieEr", image: "5"),
ANewTankInfo(name: "BaoEr", image: "6"),
ANewTankInfo(name: "09", image: "7"),
ANewTankInfo(name: "10", image: "8"),
]
init() {
let navBarAppearance = UINavigationBarAppearance()
navBarAppearance.titleTextAttributes = [.foregroundColor:UIColor.systemRed, .font: UIFont(name: "ArialRoundedMTBold", size: 20)]
navBarAppearance.largeTitleTextAttributes = [.foregroundColor:UIColor.systemBlue, .font: UIFont(name: "ArialRoundedMTBold", size: 40)]
navBarAppearance.setBackIndicatorImage(UIImage(systemName: "arrow.turn.up.left"), transitionMaskImage: UIImage(systemName: "arrow.turn.up.left"))
UINavigationBar.appearance().standardAppearance = navBarAppearance;
UINavigationBar.appearance().scrollEdgeAppearance = navBarAppearance;
UINavigationBar.appearance().compactAppearance = navBarAppearance;
UINavigationBar.appearance().tintColor = .purple
}
var body: some View {
NavigationView {
List(tankInfo) { aTankInfo in
NavigationLink(destination: TankDetailInfo(tankInfo: aTankInfo)) {
NewBasicRowView(aTankInfo: aTankInfo)
}
}
.navigationBarTitle("坦克大賞", displayMode: .automatic)
}
}
}
struct SwiftUINavi_Previews: PreviewProvider {
static var previews: some View {
if #available(iOS 15.0, *) {
SwiftUINavi()
.previewInterfaceOrientation(.portrait)
} else {
// Fallback on earlier versions
}
}
}
struct NewBasicRowView: View {
var aTankInfo: ANewTankInfo
var body: some View {
HStack {
Image(aTankInfo.image)
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Text(aTankInfo.name)
.font(.system(size: 15))
.foregroundColor(.green)
}
}
}
struct ANewTankInfo: Identifiable {
let id = UUID()
var name: String
var image: String
}
struct TankDetailInfo: View {
// @Environment 後面的內容會講到
@Environment(\.presentationMode) var mode
var tankInfo: ANewTankInfo
var body: some View {
VStack {
Image(tankInfo.image)
.resizable()
.aspectRatio(contentMode: .fit)
Text(tankInfo.name)
.font(.system(.title, design: .rounded))
.fontWeight(.bold)
Spacer()
}
.navigationBarTitle("", displayMode: .inline)
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action: {
self.mode.wrappedValue.dismiss()
}, label:{
Image(systemName: "chevron.left.circle.fill").font(.largeTitle)
.foregroundColor(.red)
})
)
}
}
2.12 模態視圖
struct SwiftUIModality: View {
var tankInfos = [
ATankInfoModaly(name: "M1A2", image: "1"),
ATankInfoModaly(name: "T90", image: "2"),
ATankInfoModaly(name: "T72", image: "3"),
ATankInfoModaly(name: "99A", image: "4"),
ATankInfoModaly(name: "LieKeLieEr", image: "5"),
ATankInfoModaly(name: "BaoEr", image: "6"),
ATankInfoModaly(name: "09", image: "7"),
ATankInfoModaly(name: "10", image: "8"),
]
// 目前有個疑問:把SwiftUIModality中目前起作用的.sheet語句註釋掉,把現在已註釋的其他語句放開,則無法其作用,不知道爲什麼
// 有高手知道的話,辛苦告知
// @State private var show = false
@State var selectTankInfo: ATankInfoModaly?
var body: some View {
NavigationView {
List(tankInfos) { aTankInfo in
BigImageRowModaly(aTankInfo: aTankInfo)
.onTapGesture {
self.selectTankInfo = aTankInfo
// self.show = true
}
}
.sheet(item:self.$selectTankInfo) { atankinfo in
DetailViewModaly(aTankInfo: atankinfo)
}
.navigationTitle("坦克大賞")
// .sheet(isPresented: self.$show) {
// if let tankInfo = self.$selectTankInfo {
// DetailViewModaly(aTankInfo: tankInfo!)
// }
// }
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct SwiftUIModality_Previews: PreviewProvider {
static var previews: some View {
SwiftUIModality()
}
}
struct ATankInfoModaly: Identifiable {
let id = UUID()
var name: String
var image: String
}
struct DetailViewModaly: View {
var aTankInfo: ATankInfoModaly
@Environment(\.presentationMode) var presentationMode
@State private var showAlert = false
var body: some View {
if #available(iOS 15.0, *) {
VStack {
Image(aTankInfo.image)
.resizable()
.scaledToFit()
.cornerRadius(5)
Text(aTankInfo.name)
.font(.system(size: 15))
.foregroundColor(.green)
}
.edgesIgnoringSafeArea(.top)
.overlay {
HStack {
Spacer()
VStack {
Button {
self.showAlert = true
// 請注意,爲何不是把alert寫在這裏!
// 目前我的理解是,寫在這裏的話,導致SwiftUI無法構造合適的View
/*
Alert(title: Text("提醒"), message: Text("確定要離開嗎"), primaryButton: .default(Text("確定"), action: {
self.presentationMode.wrappedValue.dismiss()
} ), secondaryButton: .cancel(Text("關閉")))
*/
} label: {
Image(systemName: "chevron.down.circle.fill")
.font(.largeTitle)
.foregroundColor(.white)
}
.padding(.trailing, 20)
.padding(.top, 40)
Spacer()
}
}
}.alert(isPresented: self.$showAlert) {
Alert(title: Text("提醒"), message: Text("確定要離開嗎"), primaryButton: .default(Text("確定"), action: {
self.presentationMode.wrappedValue.dismiss()
} ), secondaryButton: .cancel(Text("關閉")))
}
} else {
// Fallback on earlier versions
}
}
}
struct BigImageRowModaly : View {
var aTankInfo: ATankInfoModaly
var body: some View {
VStack {
Image(aTankInfo.image)
.resizable()
.scaledToFit()
.cornerRadius(5)
Text(aTankInfo.name)
.font(.system(size: 15))
.foregroundColor(.green)
Spacer()
}
}
}
上述代碼的實際效果如下圖所示。
2.13 表單
// 主文件
struct SwiftUIForm: View {
@State var tankInfos = [
ATankInfoForm(name: "M1A2", image: "1", type:"美國", phone: "119", priceLevel: 3, isFavorite: true, isCheckIn: false),
ATankInfoForm(name: "T90", image: "2", type:"俄羅斯", phone: "119", priceLevel: 2, isFavorite: false, isCheckIn: true),
ATankInfoForm(name: "T72", image: "3", type:"俄羅斯", phone: "119", priceLevel: 2, isFavorite: false, isCheckIn: true),
ATankInfoForm(name: "99A", image: "4", type:"俄羅斯", phone: "119", priceLevel: 2, isFavorite: false, isCheckIn: true),
ATankInfoForm(name: "LieKeLieEr", image: "5", type:"德國", phone: "123", priceLevel: 5, isFavorite: true, isCheckIn: true),
ATankInfoForm(name: "BaoEr", image: "6", type:"德國", phone: "123", priceLevel: 2, isFavorite: false, isCheckIn: true),
ATankInfoForm(name: "09", image: "7", type:"日版", phone: "777", priceLevel: 1, isFavorite: true, isCheckIn: true),
ATankInfoForm(name: "10", image: "8", type:"日本", phone: "911", priceLevel: 4, isFavorite: false, isCheckIn: false),
]
@State private var selectedRestaurant: ATankInfoForm?
@State private var showSettings: Bool = false
var body: some View {
NavigationView {
List {
ForEach(tankInfos) { aTankInfo in
BasicImageRow(tankinfo: aTankInfo)
.contextMenu {
Button(action: {
// mark the selected restaurant as check-in
self.checkIn(item: aTankInfo)
}) {
HStack {
Text("Check-in")
Image(systemName: "checkmark.seal.fill")
}
}
Button(action: {
// delete the selected restaurant
self.delete(item: aTankInfo)
}) {
HStack {
Text("Delete")
Image(systemName: "trash")
}
}
Button(action: {
// mark the selected restaurant as favorite
self.setFavorite(item: aTankInfo)
}) {
HStack {
Text("Favorite")
Image(systemName: "star")
}
}
}
.onTapGesture {
self.selectedRestaurant = aTankInfo
}
}
.onDelete { (indexSet) in
self.tankInfos.remove(atOffsets: indexSet)
}
}
.navigationBarTitle("坦克大賞")
.navigationBarItems(trailing:
Button(action: {
self.showSettings = true
}, label: {
Image(systemName: "gear").font(.title)
.foregroundColor(.black)
})
)
.sheet(isPresented: $showSettings) {
SettingView()
}
}
}
func delete(item restaurant: ATankInfoForm) {
if let index = self.tankInfos.firstIndex(where: { $0.id == restaurant.id }) {
self.tankInfos.remove(at: index)
}
}
func setFavorite(item restaurant: ATankInfoForm) {
if let index = self.tankInfos.firstIndex(where: { $0.id == restaurant.id }) {
self.tankInfos[index].isFavorite.toggle()
}
}
func checkIn(item restaurant: ATankInfoForm) {
if let index = self.tankInfos.firstIndex(where: { $0.id == restaurant.id }) {
self.tankInfos[index].isCheckIn.toggle()
}
}
}
struct SwiftUIForm_Previews: PreviewProvider {
static var previews: some View {
SwiftUIForm()
}
}
struct ATankInfoForm: Identifiable {
let id = UUID()
var name: String
var image: String
var type: String
var phone: String
var priceLevel: Int
var isFavorite: Bool = false
var isCheckIn: Bool = false
}
struct BasicImageRow: View {
var tankinfo: ATankInfoForm
var body: some View {
HStack {
Image(tankinfo.image)
.resizable()
.frame(width: 60, height: 60)
.clipShape(Circle())
.padding(.trailing, 10)
VStack(alignment: .leading) {
HStack {
Text(tankinfo.name)
.font(.system(.body, design: .rounded))
.bold()
Text(String(repeating: "$", count: tankinfo.priceLevel))
.font(.subheadline)
.foregroundColor(.gray)
}
Text(tankinfo.type)
.font(.system(.subheadline, design: .rounded))
.bold()
.foregroundColor(.secondary)
.lineLimit(3)
Text(tankinfo.phone)
.font(.system(.subheadline, design: .rounded))
.foregroundColor(.secondary)
}
Spacer()
.layoutPriority(-100)
if tankinfo.isCheckIn {
Image(systemName: "checkmark.seal.fill")
.foregroundColor(.red)
}
if tankinfo.isFavorite {
// Spacer()
Image(systemName: "star.fill")
.foregroundColor(.yellow)
}
}
}
}
// 設置視圖;請注意:用戶選擇如何反映到UI上
struct SettingView: View {
@Environment(\.presentationMode) var presentationMode
private var displayOrders = [ "Alphabetical", "Show Favorite First", "Show Check-in First"]
@State private var selectedOrder = 0
@State private var showCheckInOnly = false
@State private var maxPriceLevel = 5
var body: some View {
NavigationView {
Form {
Section(header: Text("SORT PREFERENCE")) {
Picker(selection: $selectedOrder, label: Text("Display order")) {
ForEach(0 ..< displayOrders.count, id: \.self) {
Text(self.displayOrders[$0])
}
}
}
Section(header: Text("FILTER PREFERENCE")) {
Toggle(isOn: $showCheckInOnly) {
Text("Show Check-in Only")
}
Stepper(onIncrement: {
self.maxPriceLevel += 1
if self.maxPriceLevel > 5 {
self.maxPriceLevel = 5
}
}, onDecrement: {
self.maxPriceLevel -= 1
if self.maxPriceLevel < 1 {
self.maxPriceLevel = 1
}
}) {
Text("Show \(String(repeating: "$", count: maxPriceLevel)) or below")
}
}
}
.navigationBarTitle("Settings")
.navigationBarItems(leading:
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Cancel")
.foregroundColor(.black)
})
, trailing:
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Save")
.foregroundColor(.black)
})
)
}
}
}
struct SettingView_Previews: PreviewProvider {
static var previews: some View {
SettingView()
}
}
本小節所用到的UI組件比較多,但是和以前相似,沒有多大的難度,請大家最好自己動手敲一敲代碼,如果出現問題就自己解決。