实战编程·刻在男人DNA里的浪漫,空气投篮(一)
项目背景
和往常一样的下午,午后的阳光洒在身上,感觉有些舒服。突然脑海里闪过一个念头,小步快跑,起身,双手举起,目视前方,挥手投篮,唰.........最近一款APP几乎引起了所有男孩子的兴趣,这就是空气投篮。
想起年少时和一群朋友走在路上,男孩子们都或多或少做过这种“傻傻的”动作,那是青春的味道。
而竟然有家公司将它做了出来,一时间马上下载、安装.......额.......这.......需要AppleWatch才能用。好吧,竟然没有办法体验空气投篮,不如就用用自己的专长,画画iOS端的页面吧。
项目搭建
首先打开Xcode,创建一个新的SwiftUI项目,命名为AirBall,如下图所示:
空气投篮分为iOS端和Watch,本章我们先来完成iOS的相关页面。iOS端页面操作及其流程如下图所示:
实战编程
准备游戏视图
首先是准备游戏视图,简单分析可知,它由文字Text和图片Image组成,背景颜色填充为黑色。Image图片部分,需要导入一张SVG适量图片,使得其很好地与背景融合。如下图所示:
导入完成后,我们来构建页面部分。在ContentView文件中,我们键入以下代码:
// 准备游戏 func prepareView() -> some View { VStack(alignment: .center, spacing: 80) { Spacer() Text("请确定你已启用Apple Watch上的空气投篮App") .font(.system(size: 17)) .foregroundColor(.white) .lineLimit(2) .lineSpacing(15) .multilineTextAlignment(.center) Image("watch_application") .resizable() .aspectRatio(contentMode: .fit) Spacer() Spacer() }.frame(maxWidth: Constants.screenWidth / 2) }
上述代码中,我们创建了一个新的View视图prepareView准备开始游戏视图。
在prepareView视图中,文字Text和Image图片使用VStack垂直布局容器包裹,并设置其对齐方式为居中对齐,容器内容元素间距为80。
Text文字部分的处理为设置font字体为17号字,设置foregroundColor填充色为白色,由于文字过长可能导致页面无法展示的原因,这里设置lineLimit文字显示行数为2行,并设置换行时multilineTextAlignment文字对齐方式为居中对齐。
Image图片部分,设置resizable图片缩放,并设置aspectRatio保持原本的宽高比避免变形。
最后使用frame设置VStack垂直布局容器的宽度,为屏幕宽度的一半。为了增强用户体验,在VStack垂直布局容器中使用Spacer占位符,下方设置2个,上方设置一个,这个视觉元素就会展示在屏幕上部分2/3的位置,又是一个小技巧。
完成后,我们在Body中展示,如下代码所示:
ZStack { Color(.black).edgesIgnoringSafeArea(.all) prepareView() }
上述代码中,我们使用ZStack叠加视图,将prepareView准备游戏视图和Color颜色叠加,颜色部分使用edgesIgnoringSafeArea忽略全部安全区域,便可让黑色背景铺满整个屏幕。
游戏列表视图
打开App,进入准备游戏视图,此时需要与Watch端联动,在Watch端确认后,iOS端将进入至游戏列表页面。
游戏列表页面的交互逻辑是,点击游戏卡片则进入到游戏中,在游戏列表页左右滑动可切换游戏。
我们先来完成单张游戏卡片的设计。分析得知,单张游戏卡片的内容包括3块内容:游戏项目、游戏说明、游戏封面。
我们创建一个新的视图,代码如下所示:
// MARK: 游戏项 struct gameRowView: View { var gameName: String var gameHelpText: String var gameImage: String var body: some View { VStack(alignment: .center, spacing: 60) { Text(gameName) .font(.system(size: 48)) .bold() .foregroundColor(.white) VStack(alignment: .center, spacing: 10) { Text(gameHelpText) .font(.system(size: 17)) .foregroundColor(.white) Image(gameImage) .resizable() .aspectRatio(contentMode: .fit) .frame(maxHeight:UIScreen.main.bounds.size.width - 20) } } } }
上述代码中,我们创建了一个新的结构体gameRowView游戏项视图。
之所以创建一个新的结构体,而没有和上面prepareView准备游戏视图一样直接定义View视图,是因为我们需要将gameRowView游戏项视图作为“母版”,然后按照单个gameRowView游戏项视图构建多个一样样式的游戏卡片。
在gameRowView游戏项视图中,我们声明了3个String类型的变量:gameName游戏名称、gameHelpText游戏说明、gameImage游戏封面。
然后在其Body中创建样式,由于游戏名称的Text和其余两块在页面上还是有些距离,这里使用了2个VStack垂直布局容器,将游戏说明和游戏封面放在一个容器中,它们直接的间距为10,而再用一个VStack垂直布局容器再把游戏标题包裹在一起,间距为60。
这里额外再补充一个知识点。
就是游戏封面使用frame设置其大小的问题,由于我们导入的游戏封面图片可能存在大小不一致的问题,因此如果需要让这个游戏卡片看起来元素位置保持一致,由于使用了VStack垂直布局容器,因此,可以设置图片maxHeight高度为屏幕width宽度,再减去20留点边距。
这样做,无论图片尺寸是多少,每个游戏卡片展示的游戏名称、游戏说明、游戏封面的位置就保持一致了。
我们导入两张SVG格式的游戏图片作为素材使用,如下图所示:
游戏卡片是左右滑动切换的交互,这时我们就可以在ContentView视图中再创建一个View视图构建它,如下代码所示:
// 游戏列表 func gameListView() -> some View { TabView { gameRowView(gameName: "投篮", gameHelpText: "手举球开始游戏", gameImage: "basketball") gameRowView(gameName: "打棒球", gameHelpText: "双手挥动开始游戏", gameImage: "baseball") } .tabViewStyle(PageTabViewStyle()) }
上述代码中,我们创建了一个游戏列表视图gameListView。
然后使用TabView滚动视图容器包裹了2个gameRowView游戏项视图,游戏项视图中我们给声明的变量赋值以显示内容。最后设置TabView滚动视图的样式,为PageTabViewStyle分页滚动类型,如此便实现了横向切换游戏卡片的交互。
本章代码
为方便学习,本章完整代码如下所示:
import SwiftUI struct ContentView: View { var body: some View { ZStack { Color(.black).edgesIgnoringSafeArea(.all) gameListView() } } // 准备游戏 func prepareView() -> some View { VStack(alignment: .center, spacing: 80) { Spacer() Text("请确定你已启用Apple Watch上的空气投篮App") .font(.system(size: 17)) .foregroundColor(.white) .lineLimit(2) .lineSpacing(15) .multilineTextAlignment(.center) Image("watch_application") .resizable() .aspectRatio(contentMode: .fit) Spacer() Spacer() }.frame(maxWidth: UIScreen.main.bounds.size.width / 2) } // 游戏列表 func gameListView() -> some View { TabView { gameRowView(gameName: "投篮", gameHelpText: "手举球开始游戏", gameImage: "basketball") gameRowView(gameName: "打棒球", gameHelpText: "双手挥动开始游戏", gameImage: "baseball") } .tabViewStyle(PageTabViewStyle()) } } // MARK: 游戏项 struct gameRowView: View { var gameName: String var gameHelpText: String var gameImage: String var body: some View { VStack(alignment: .center, spacing: 60) { Text(gameName) .font(.system(size: 48)) .bold() .foregroundColor(.white) VStack(alignment: .center, spacing: 10) { Text(gameHelpText) .font(.system(size: 17)) .foregroundColor(.white) Image(gameImage) .resizable() .aspectRatio(contentMode: .fit) .frame(maxHeight: UIScreen.main.bounds.size.width - 20) } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
本章小结
首先恭喜你,完成了本章的介绍的所有内容!
空气投篮iOS端的页面目前我们只完成了前2张,接下来,我们将继续完成其余的页面及其交互,以及后面也会切换到Watch端,完成空气投篮Watch端的相关页面设计。
总的来说,空气投篮App的页面及其交互并不复杂。
这个项目很小,却实实在在戳中了很多用户的内心。仿佛某些个人“很傻”的小习惯被大众所认知、所接受,这种满足感刚好刺激到了某个痛点,很小,但很痛。
这可能就是一款好的产品的象征,也应该是每个提供产品服务的企业所追求的。
望共勉之~