项目背景

又到了吃饭的时刻了,翻开一些餐饮App翻来翻去都不知道想吃什么,感觉全部都吃过了,看到都有点儿腻。

有没有一个App能够帮我随机引荐吃什么的呢?想了想,爽性我自己写一个吧!

说干就干。

全文约3500字,估计阅览时长为5分钟,实操时长约15分钟

项目建立

首要,创立一个新的SwiftUI项目,命名为MyMenu

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

Model部分

数据模型

首要是数据部分的预备,咱们创立一个新的Swift文件,命名为Model.swift

import SwiftUI
class Model: Decodable {
var foodTime: String
var foodName: String
var foodImageURL: String
}

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

上述代码中,咱们创立了一个Model类,遵从Decodable协议。

Decodable协议能够协助咱们解析来自网络恳求中的Json数据格式,咱们声明晰3个String类型的变量:餐段foodTime、食物称号foodName、食物图片foodImageURL

回到ContentView文件,运用@State修饰符声明一个数组存在Model数据,示例:

@State var models: [Model] = []

Json数据

数据源部分,咱们运用第三方网站工具,生成Json数据,示例:

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

咱们拿到了Json数据的地址,咱们也在ContentView文件中声明,示例:

let DataURL = "https://api.npoint.io/4e97acfc3e5f73300779"

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

这样咱们就完结了基础的数据预备。

View部分

色彩拓宽

为了更好地运用16进制色彩值,咱们对Color进行拓宽。创立一个新的Swift文件,命名为ColorHexString

import SwiftUI
extension Color {
static func rgb(_ red: CGFloat, green: CGFloat, blue: CGFloat) -> Color {
return Color(red: red / 255, green: green / 255, blue: blue / 255)
}
static func Hex(_ hex: UInt) -> Color {
let r: CGFloat = CGFloat((hex & 0xFF0000) >> 16)
let g: CGFloat = CGFloat((hex & 0x00FF00) >> 8)
let b: CGFloat = CGFloat(hex & 0x0000FF)
return rgb(r, green: g, blue: b)
}
}

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

这样咱们就能够在接下来的View页面款式中直接运用16进制色彩值了。

标题

先声明一个变量存储当时餐段信息,后边咱们会经过当时时刻来判别现在归于哪一个餐段。示例:

var DefaultTime:String = "午饭"

然后咱们构建一个标题视图,并在ContentView视图中展现。示例:

// 标题
func TitleView(time:String) -> some View {
HStack {
Text("当时餐段 : "+time)
.font(.title2)
.fontWeight(.bold)
Spacer()
Image(systemName: "rectangle.grid.1x2.fill")
.foregroundColor(Color.Hex(0x67C23A))
}
.padding(.horizontal)
.padding(.top)
}

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

上述代码中,咱们界说了一个TitleView办法,传入标题参数,回来View视图。

咱们运用Text作为标题,运用String字符串拼接办法展现,另外运用Image构建了一个切换餐段的图标,之后的交互中会运用。

引荐成果

引荐成果部分由餐品图片餐品称号组成,咱们也声明2个变量存储它,示例:

var DefaultImageURL:String = "https://img0.baidu.com/it/u=156558209,1663147989&fm=253&fmt=auto&app=138&f=JPEG?w=626&h=500"
var DefaultName:String = "今日想吃点啥?"

引荐成果款式部分,咱们采用最简单的纵向布局进行组合,示例:

//引荐成果
func CardView(imageURL: String, name: String) -> some View {
VStack {
AsyncImage(url: URL(string: imageURL))
.aspectRatio(contentMode: .fit)
.frame(minWidth: 120, maxWidth: .infinity, minHeight: 120, maxHeight: .infinity)
Text(name)
.font(.system(size: 17))
.fontWeight(.bold)
.foregroundColor(.black)
.padding()
}
.cornerRadius(10)
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.Hex(0x67C23A), lineWidth: 2))
.padding([.top, .horizontal])
}

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

上述代码中,咱们界说了一个办法CardView,传入imageURLname,回来一个View视图。

CardView视图中,咱们运用AsyncImage来创立餐品图片,然后运用Text来展现餐品称号,而且给整个视图overlay加了边框线。

引荐按钮

同样的办法,咱们创立一个引荐按钮,用于随机挑选餐品,先完结款式部分,示例:

//引荐按钮
func ChooseBtn() -> some View {
Button(action: {
}) {
Text("一键引荐")
.font(.system(size: 17))
.fontWeight(.bold)
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.foregroundColor(.white)
.background(Color.Hex(0x67C23A))
.cornerRadius(5)
.padding(.horizontal, 20)
.padding(.bottom)
}
}

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

整体款式作用

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

作用不错!

接下来才是有趣的地方,敲黑板!开始要写逻辑了!

ViewModel部分

取得当时餐段

咱们创立一个新的Swift文件,命名为ViewModel.swift

关于体系取得餐段的思路,咱们能够这么考虑,咱们先取得当时体系的时刻,依据体系时刻所在的时刻段,来更新餐段。示例:

import SwiftUI
class ViewModel: ObservableObject {
// 当时餐段
@Published var currentTimeName: String = ""
init() {
updateTime()
}
// 餐段枚举
enum MealTimeName: String {
case breakfast = "早餐"
case lunch = "午饭"
case afternoonTea = "下午茶"
case supper = "晚餐"
case nightSnack = "宵夜"
}
// 获取当时体系时刻
func getCurrentTime() -> Int {
let dateformatter = DateFormatter()
dateformatter.dateFormat = "HH"
return Int(dateformatter.string(from: Date()))!
}
// 更新当时餐段
func updateTime() {
if getCurrentTime() < 10 {
currentTimeName = MealTimeName.breakfast.rawValue
} else if getCurrentTime() >= 10 && getCurrentTime() < 14 {
currentTimeName = MealTimeName.lunch.rawValue
} else if getCurrentTime() >= 14 && getCurrentTime() < 16 {
currentTimeName = MealTimeName.afternoonTea.rawValue
} else if getCurrentTime() >= 16 && getCurrentTime() < 20 {
currentTimeName = MealTimeName.supper.rawValue
} else {
currentTimeName = MealTimeName.nightSnack.rawValue
}
}
}

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

上述代码中,咱们先声明晰一个变量currentTimeName,来作为更新餐段的参数。

然后设置了一个餐段称号的枚举MealTimeName,来表示餐段和对应餐段的称号。

再是界说了一个办法getCurrentTime取得当时时刻,只取值到小时,再界说了一个办法updateTime来依据取得到的时刻和一些时刻段做比较,更新currentTimeName的值。

最终在init调用时调用updateTime更新办法,就得到了当时的餐段currentTimeName的准确值。

更新当时餐段

咱们回到ContentView文件中,首要将原先声明的变量DefaultTime加一个存储办法,示例:

@State var DefaultTime:String = "午饭"

然后引进ViewModel的内容,示例:

@ObservedObject private var viewModel = ViewModel()

在主视图展现时,更新当时餐段,示例:

.onAppear(){
 DefaultTime = viewModel.currentTimeName
}

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

切换当时餐段

App除了依据体系时刻主动判别餐段外,咱们还能够增加一个可供用户手艺切换餐段的交互。

咱们能够运用Sheet弹窗来做切换,首要先创立款式部分,示例:

// 切换餐段
private var ChooseTimeSheet: ActionSheet {
let action = ActionSheet(
title: Text("餐段"),message: Text("请挑选餐段"),buttons:[
.default(Text("早餐"), action: {self.DefaultTime = "早餐"}),
.default(Text("午饭"), action: {self.DefaultTime = "午饭"}),
.default(Text("下午茶"), action: {self.DefaultTime = "下午茶"}),
.default(Text("晚餐"), action: {self.DefaultTime = "晚餐"}),
.default(Text("宵夜"), action: {self.DefaultTime = "宵夜"}),
.cancel(Text("取消"), action: {})
]
)
return action
}

咱们创立了一个Sheet弹窗,它有几个可选项,当咱们点击不同餐段称号时,更新DefaultTime餐段的值。

Sheet弹窗款式创立好后,咱们声明一个变量来供点击触发,示例:

@State var showChooseTimeSheet: Bool = false

然后在ContentView视图中调用Sheet弹窗,示例:

// 挑选餐段
.actionSheet(isPresented: $showChooseTimeSheet, content: { ChooseTimeSheet })

至于触发条件,咱们加在点击TitleView标题视图右边的Image上,示例:

.onTapGesture {
 self.showChooseTimeSheet.toggle()
}

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

不错不错!

网络恳求数据

让咱们回到ViewModel.swift文件,咱们来完结网络恳求部分。

@Published var currentTimeName: String = ""
@Published var currentImageURL: String = ""
@Published var currentName: String = ""
@Published var models: [Model] = []
let DataURL = "https://api.npoint.io/4e97acfc3e5f73300779"

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

首要,咱们要声明好ViewModel需要的信息,后边在View中进行赋值,咱们声明晰餐品图片地址currentImageURL、餐品称号currentName、存储的数组models,还有恳求数据的地址DataURL

然后是网络恳求部分,示例:

// 网络恳求
func getMenu() {
let session = URLSession(configuration: .default)
session.dataTask(with: URL(string: DataURL)!) { data, _, _ i
guard let jsonData = data else { return }
do {
let meals = try JSONDecoder().decode([Model].self, from: jsonData)
self.models = meals
} catch {
print(error)
}
}
.resume()
}

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

上述代码中,咱们界说了一个办法getMenu,经过URLSession取得数据源地址DataURL的数据,而且解析到models中。

这样在调用getMenu办法时,咱们就能够从DataURL地址中取得Json格式的数据,并解析数据按照咱们Model声明好的参数进行存储。

挑选餐段数据

下一步,由于咱们恳求回来的数据是一切餐段的数据,而咱们每次App引荐的是单个餐段的数据,那么咱们还需要从恳求回来的一切数据傍边挑选当时挑选的餐段的数据。示例:

// 依据餐段取得餐品信息
func getMealMessage(time:String) {
let query = time.lowercased()
DispatchQueue.global(qos: .background).async {
let filter = self.models.filter { $0.foodTime.lowercased().contains(query) }
DispatchQueue.main.async {
withAnimation(.spring()) {
self.models = filter
}
}
}
}

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

上述代码中,咱们界说了一个办法getMealMessage,传入String类型的餐段时刻time,然后将time作为匹配项,与models数组中的foodTime进行匹配关联。

找到餐段时刻和数组中的餐段时刻一致的数据,就把相关数据从头存储到models数组中,这样咱们依据餐段挑选出来了餐品信息。

随机引荐餐品

咱们经过网络恳求getMenu办法取得了一切餐段的餐品数据,再经过getMealMessage办法依据餐段挑选出来本餐段的数据,下一步就是在这个餐段的数据中随机引荐餐品,示例;

//随机引荐菜品
func getRandomFood() {
let index = Int(arc4random() % UInt32(models.count))
currentName = models[index].foodName
currentImageURL = models[index].foodImageURL
}

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

上述代码中,咱们界说了一个办法getRandomFood,在办法中,咱们从models数组中的一切数据总量生成一个随机数index,然后餐品称号currentName赋值models数组中随机数index下标的foodName,同理餐品图片currentImageURL也是。

这样咱们就得到了一个取得该餐段随机餐品的办法。

咱们先在viewModel初始化时,调用取得餐品数据和依据餐段挑选产品的办法。示例:

init() {
    updateTime()
    getMenu()
}

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

ViewModel办法调用

咱们回到ContentView.swift文件,咱们在View中依据事务调用ViewModel中的办法。

首要,原先声明的变量都需要运用@State关键字,以便于实现存储。示例:

@State var DefaultTime: String = "午饭"
@State var DefaultImageURL: String = "https://img0.baidu.com/it/u=156558209,1663147989&fm=253&fmt=auto&app=138&f=JPEG?w=626&h=500"
@State var DefaultName: String = "今日想吃点啥?"

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

然后在ChooseBtn按钮上增加交互动作,当咱们点击一键引荐时,查找依据当时餐段挑选数据,然后调用随机餐品的办法,最终将餐品称号和餐品图片赋值到View中,示例:

// 引荐按钮
func ChooseBtn() -> some View {
Button(action: {
viewModel.getMealMessage(time: DefaultTime)
viewModel.getRandomFood()
DefaultImageURL = viewModel.currentImageURL
DefaultName = viewModel.currentName
}) {
Text("一键引荐")
.font(.system(size: 17))
.fontWeight(.bold)
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.foregroundColor(.white)
.background(Color.Hex(0x67C23A))
.cornerRadius(5)
.padding(.horizontal, 20)
.padding(.bottom)
}
}

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

当然不要忘了,咱们还有切换餐段的功能呢,在切换餐段时,咱们还需要从头赋值。示例:

self.DefaultTime = "早餐"
viewModel.getMenu()
DefaultImageURL = "https://img0.baidu.com/it/u=156558209,1663147989&fm=253&fmt=auto&app=138&f=JPEG?w=626&h=500"
DefaultName = "今日想吃点啥?"

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

上述代码中,当咱们切换餐段的时候,除了餐段时刻DefaultTime从头赋值外,咱们还调用网络恳求从头更新models数组的数据,以及将餐品称号和餐品图片。

点击按钮预览下作用:

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

交互动画

动画部分是SwiftUI的灵魂,承接着用户和App之间沟通的渠道。

动画部分咱们能够做简单一点,比如在引荐时给个加载动画,引荐成功后展现引荐成果。

Loading动画

咱们创立一个新的SwiftUI文件,命名为LoadingView

import SwiftUI
struct LoadingView: View {
@State var show: Bool = false
var body: some View {
Image(systemName: "sun.min.fill")
.resizable()
.foregroundColor(Color.Hex(0xFAD0C4))
.aspectRatio(contentMode: .fit)
.frame(width: 60, height: 60)
.rotationEffect(.degrees(show ? 360 : 0))
.onAppear(perform: {
doAnimation()
})
}
func doAnimation() {
withAnimation(Animation.easeInOut(duration: 1).repeatForever(autoreverses: true)) {
show.toggle()
}
}
}

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

咱们创立了一个Image,然后让它主动旋转,到达加载中的作用。由于之前咱们就用过这段代码,这里就做太多的解说了。

交互动画运用

咱们回到ContentView.swift文件,声明一个变量来判别是否展现成果,示例:

@State var showResult: Bool = false

然后依据showResult的值来展现成果还是加载LoadingView动画,示例:

if !showResult {
CardView(imageURL: DefaultImageURL, name: DefaultName)
} else {
LoadingView()
}

最终,咱们在ChooseBtn视图点击一键引荐时,进行展现成果的切换,示例:

self.showResult = true
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0) {
self.showResult = false
DefaultImageURL = viewModel.currentImageURL
DefaultName = viewModel.currentName
}

上述代码中,咱们在点击一键引荐时,首要修正showResult的值,展现Loading,然后在1秒之后,咱们再修正showResult的值,并赋值从头展现引荐成果

项目展现

每天不知道吃什么,用SwiftUI写个随机推荐App帮你做决定吧!

不错不错!

假如本专栏对你有协助,不妨点赞、评论、关注~

我正在参与技能社区创作者签约方案招募活动,点击链接报名投稿。