ios入门实例(四): 随机数游戏

1,178 阅读4分钟

前言:

实战demo:控制随机生成1~6的数字完成1个至多个的骰子类效果,使用到的技术点:@State,Button,resizable,aspectRatio,.labelStyle,disabled,通过更新@State的值自动更新视图(与React state相似)

摇骰子最终效果:

final.gif

创建DiceRoller项目 和 DiceView 页面

通过XCode创建项目:DiceRoller(点击查看xcode创建项目)

创建siwftUI页面:DiceView(点击查看xcode创建siwftUI页面

DiceView页面添加的骰子icon

// DiceView.swift

import SwiftUI struct DiceView: View { 
    var body: some View { 
-        Text("Hello, World!")    
+        Image(systemName: "die.face.1") 
    } 
} 

#Preview { 
  DiceView() 
}
image.png

这里使用到了Xcode 集成 SF Symbols字体图标(点击查看有哪些具体图标),其中die.face.x x可以为1~6对应骰子点数样式可以在XCode中查看对应文档

image.png

修改DiceView样式变大且通过 var numberOfPips变量替换 systemName结尾的数字来控制点数对应的字体图标样式

// DiceView.swift

import SwiftUI struct DiceView: View { 
+   var numberOfPips: Int = 1

    var body: some View { 
-      Image(systemName: "die.face.1") 
+      Image(systemName: "die.face.\(numberOfPips)") 
+        .resizable()
+        .frame(width: 100, height: 100)  
    } 
} 

#Preview { 
  DiceView() 
}
image.png

其中 resizable会让image填充到整个页面的空白,再通过frame规定长宽来达到放大image图标的效果

@State控制UI显示的骰子点数

首先需要在点数icon竖直下方添加个Button ,使用VStack 使得 点数icon 与 Button竖直排列,和控制numberOfPips的值

// DiceView.swift

import SwiftUI struct DiceView: View { 
    var numberOfPips: Int = 1

    var body: some View { 
+    VStack{
       ...
+       Button("Roll") { 
+         numberOfPips = Int.random(in: 1...6) 
+        }
+    }
    } 
} 

#Preview { 
  DiceView() 
}

这时候XCode会报错:Cannot assign to property: 'self' is immutable

image.png

由于var numberOfPips 是一个长量不能被赋值,但我又想通过改变numberOfPips达到骰子点数变化,这时候就需要使用 @State:既可以动态赋值又可以触发UI update 视图(前端React state近似)

修改 numberOfPips@State属性和样式调整

// DiceView.swift

import SwiftUI struct DiceView: View { 
-    var numberOfPips: Int = 1
+    @State private var numberOfPips: Int = 1

    var body: some View { 
    VStack{
       ...
       Button("Roll") { 
         numberOfPips = Int.random(in: 1...6) 
        }
+       .buttonStyle(.bordered)
     }
    } 
} 

#Preview { 
  DiceView() 
}
1.gif

简单的摇骰子功能就实现了,但是icon显示的有点生硬,使用withAnimation(点击查看文档)加点动画过度下

// DiceView.swift

       ...
       Button("Roll") { 
+         withAnimation {
           numberOfPips = Int.random(in: 1...6) 
+         }
        }
2.gif

这样简单的一个骰子就完成了,接下阐述设置多个骰子

ContentView 入口页动态设置骰子数量

上一步骤介绍了@State控制点数,在ContentView中也可以使用@State控制骰子个数,切换到ContentView.siwft文件中默认引用三个

//ContentView.siwft 

import SwiftUI 

struct ContentView:View { 
     @State private var numberOfDice: Int = 3
     var body: some View { 
        VStack { 
           Text("Dice Roller") 
            .font(.largeTitle.lowercaseSmallCaps()) 
           HStack { 
            ForEach(1...numberOfDice, id: \.description) {
              _ in DiceView() 
            } 
           }
        } 
        .padding() 
       } 
     } 
     
#Preview { 
   ContentView() 
}

其中:ForEach(具体文档)为方便numberOfDice动态的变化而遍历 DiceView来控制骰子数量

image.png

接下来添加Button触发变化numberOfDice达到控制骰子数量

//ContentView.siwft 
   ...
        VStack { 
           Text("Dice Roller") 
            .font(.largeTitle.lowercaseSmallCaps()) 
           HStack { 
            ForEach(1...numberOfDice, id: \.description) {
              _ in DiceView() 
            } 
           }
+       HStack { 
+          Button("减少骰子") { 
+             numberOfDice -= 1 
+          }
+            .buttonStyle(.bordered)
+          Button("添加骰子") { 
+             numberOfDice += 1 } 
+          }
+            .buttonStyle(.bordered)
+          .padding()
+       }
      }   
3.gif

Button disabled 控制最小值

当一直numberOfDice减到0时Preview崩溃,因为ForEach遍历范围从1~0 是错误的

image.png

这时候需要判断当numberOfDice == 1 button就不能点击

//ContentView.siwft 
   ...

       HStack { 
          Button("减少骰子") { 
            numberOfDice -= 1 
          }
           .buttonStyle(.bordered)
+           .disabled(numberOfDice == 1)
        ...  
       }  
4.gif

aspectRatio 适配较多骰子样式

numberOfDice大于4个后样式开始变形

image.png

这里需要修改 DiceViewImage设置为灵活的最大高度和宽度frame,和保持宽高比1:1 aspectRatio文档地址

// DiceView
     Image(systemName: "die.face.\(numberOfPips)") 
        .resizable() 
+        .frame(maxWidth: 100, maxHeight: 100)
+        .aspectRatio(1, contentMode: .fit)

可以添加多个而不会变形:

image.png

Button样式控制

使用+- icon 替换减少骰子添加骰子看起来会更简约,其中labelStyle只显示标题 icon 名称和对应样式可以查看文章第一步提到的Xcodeicon查询

//ContentView.siwft 
   ...

       HStack { 
          Button("减少骰子"
+          , systemImage: "minus.circle.fill") { 
            numberOfDice -= 1 
          }
-           .buttonStyle(.bordered)
+           .disabled(numberOfDice == 1)

          Button("添加骰子"
+          , systemImage: "plus.circle.fill") { 
            numberOfDice -= 1 
          }
-           .buttonStyle(.bordered)
       } 
       .padding()
+      .labelStyle(.iconOnly)
+      .font(.title)
...
image.png

整体样式调整

首页添加个 appBackground app 背景颜色(查看如何添加主体颜色),添加成功后修改 ContentView样式

//ContentView.siwft 

   ...
    var body: some View { 
      VStack {
      ...
      }
    }
    .padding()
+    .background(.appBackground)
...
image.png

先把Vstack高宽使用infinity撑满全屏,和.tint设置下视图和图像颜色

//ContentView.siwft 

   ...
    var body: some View { 
      VStack {
      ...
      }
    }
    .padding()
+   .frame(maxWidth: .infinity, maxHeight: .infinity)
    .background(.appBackground)
+   .tint(.white)    
...
image.png

文本:Dice Roller 也设置成白色

//ContentView.siwft 

   ...
    var body: some View { 
      VStack {
       Text("Dice Roller")
       .font(.largeTitle.lowercaseSmallCaps()) 
+       .foregroundStyle(.white)
      }
    }
...
image.png

现在骰子的颜色也统一成白色调 , 切换到 DiceView.swift代码中,骰子图标换为填充的图标(die.face.x.fill

// DiceView
     Image(systemName: 
-       "die.face.\(numberOfPips)"
+        "die.face.\(numberOfPips).fill"
       ) 
        .resizable() 
        .frame(maxWidth: 100, maxHeight: 100)
+       .foregroundStyle(.black, .white)

最终效果:

image.png

总结

本章主要是通过 @State 控制定义的变量,当变量改变后UI视图层也会更新改变,达到随机生成变量而改变骰子点数的效果,也拓展了方法:

  • aspectRatio视图比例显示
  • withAnimation动画过度
  • ForEach遍历数组
  • Button中icon的使用和disabled按钮失效
  • labelStyle iconOnly 按钮只显示icon