import SwiftUI
@main
struct MyApp: App {
@StateObject private var data = CreatureEnvZoo()
init() {
}
var body: some Scene {
WindowGroup {
NavigationStack {
StudyView().navigationTitle("我的动物")
}
.task {
data.loadData()
}
.onChange(of: data.creatures) { _ in
data.saveData()
}
.environmentObject(data)
}
}
}
struct StudyView: View {
@EnvironmentObject var envData : CreatureEnvZoo
var body: some View {
VStack {
NavigationStack {
NavLinkView()
ToggleShow()
Spacer()
List {
CreatureAniRows(argData: envData)
Section("菜单2") {
Text("2-1").offset(x: -10, y: -5)
}
}.toolbar {
ToolbarItem {
NavigationLink("添加") {
CreatureEditorTest().navigationTitle("添加动物")
}
}
}
}
.padding(0)
}
}
}
struct StudyView_Previews: PreviewProvider {
static var previews: some View {
@StateObject var data = CreatureEnvZoo()
return StudyView().environmentObject(data)
}
}
struct NavLinkView: View {
@StateObject private var stateData = CreatureViewZoo()
var body: some View {
NavigationLink {
SlidingRectangle().navigationTitle("滑动矩形")
} label: {
HStack {
Text("轻点以导航")
Spacer()
Image(systemName: "arrow.forward.circle")
}.font(.title3)
}
NavigationLink("跳转 ipad 分栏示例") {
Mapper(data: stateData).navigationTitle("ipad 分栏")
}
NavigationLink("跳转到符号集") {
LabelGrid().navigationTitle("符号集")
}
NavigationLink("布局介绍") {
HalfCard()
HalfCard().rotationEffect(.degrees(180))
}
}
}
struct SlidingRectangle: View {
@State private var width: Double = 0.1
var show = true
var body: some View {
VStack(spacing: 20) {
Slider(value: $width)
Rectangle().frame(width: width * 1000)
.overlay(alignment: .topTrailing) {
if (show) {
Text("遮罩").foregroundStyle(.white)
}
}
}
.frame(minWidth: 100).frame(height: 100)
.padding(.trailing, 20)
}
}
struct ToggleShow: View {
@State private var show = false
private var name = "字符串123"
var body: some View {
Toggle("切换", isOn: $show)
Text("这里是变量:\(name)").opacity(show ? 1 : 0)
}
}
struct Mapper: View {
@ObservedObject var data: CreatureViewZoo
@State private var selectionID: CreatureAni.ID?
func creatureRowView(ct creature: CreatureAni) -> some View {
HStack {
Text(creature.name).font(.title)
Spacer()
Text(creature.emoji)
.font(.title)
.frame(minWidth: 125)
}
}
var body: some View {
NavigationSplitView {
List(selection: $selectionID) {
ForEach(data.creatures) { creature in
self.creatureRowView(ct: creature)
}
.onDelete { indexSet in
data.creatures.remove(atOffsets: indexSet)
}
}
} detail: {
if let creature = data.creatures.first(where: { item in
item.id == selectionID
}) {
CreatureAniDetail(creature: creature).navigationTitle(creature.name)
}
}
}
}
class CreatureViewZoo : ObservableObject {
@Published var creatures = [
CreatureAni(name: "Gorilla", emoji: "🦍"),
CreatureAni(name: "Peacock", emoji: "🦚"),
]
}
class CreatureEnvZoo : ObservableObject {
@Published var creatures = [
CreatureAni(name: "霸王龙", emoji: "🦖"),
CreatureAni(name: "鱿鱼", emoji: "🦑"),
]
private static func getZooFileURL() throws -> URL {
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
.appendingPathComponent("zoo.data")
}
func loadData() {
do {
let fileURL = try CreatureEnvZoo.getZooFileURL()
let data = try Data(contentsOf: fileURL)
creatures = try JSONDecoder().decode([CreatureAni].self, from: data)
print("加载动物数量: \(creatures.count)")
} catch {
print("无法加载")
}
}
func saveData() {
do {
let fileURL = try CreatureEnvZoo.getZooFileURL()
let data = try JSONEncoder().encode(creatures)
try data.write(to: fileURL, options: [.atomic, .completeFileProtection])
print("保存动物")
} catch {
print("无法保存")
}
}
}
struct CreatureAni : Identifiable, Codable, Equatable {
var name : String
var emoji : String
var id = UUID()
var offset = CGSize.zero
var rotation : Angle = Angle(degrees: 0)
enum CodingKeys: String, CodingKey {
case name
case emoji
case id
case offset
}
}
struct CreatureAniRows: View {
@ObservedObject var argData: CreatureEnvZoo
var body: some View {
VStack {
ForEach(argData.creatures) { creature in
let index = argData.indexFor(creature)
CreatureAniRow(creature: creature, index: index)
.swipeActions {
Button(role: .destructive) {
argData.creatures.remove(atOffsets: IndexSet(integer: index))
} label: {
Label("Delete", systemImage: "trash")
}
}
}
}.onTapGesture {
argData.randomizeOffsets()
}
}
}
struct CreatureAniRow: View {
var creature : CreatureAni
var index: Int = 0
var body: some View {
HStack {
Text(creature.name)
.font(.title)
.offset(creature.offset)
.animation(.default.delay(Double(index) / 10), value: creature.offset)
.animation(.default, value: creature.offset)
Spacer()
Text(creature.emoji)
.frame(minWidth: 125)
.offset(creature.offset)
.rotationEffect(creature.rotation)
.animation(.spring(response: 0.5, dampingFraction: 0.5), value: creature.rotation)
.animation(.default, value: creature.offset)
}
}
}
extension CreatureEnvZoo {
func randomizeOffsets() {
for index in creatures.indices {
creatures[index].offset = CGSize(width: CGFloat.random(in: -20...20), height: CGFloat.random(in: -20...20))
creatures[index].rotation = Angle(degrees: Double.random(in: 0...720))
}
}
func synchronizeOffsets() {
let randomOffset = CGSize(width: CGFloat.random(in: -200...200), height: CGFloat.random(in: -200...200))
for index in creatures.indices {
creatures[index].offset = randomOffset
}
}
func indexFor(_ creature: CreatureAni) -> Int {
if let index = creatures.firstIndex(where: { $0.id == creature.id }) {
return Int(index)
}
return 0
}
}
struct CreatureEditorTest: View {
@State private var newCreature : CreatureAni = CreatureAni(name: "", emoji: "")
@EnvironmentObject var data : CreatureEnvZoo
@Environment(\.dismiss) var dismiss
var body: some View {
VStack(alignment: .leading) {
Form {
Section("名称") {
TextField("名称", text: $newCreature.name)
}
Section("表情符号") {
TextField("表情符号", text: $newCreature.emoji)
}
Section("动物预览") {
CreatureAniRow(creature: newCreature, index: 0)
}
}
}
.toolbar {
ToolbarItem {
Button("添加") {
data.creatures.append(newCreature)
dismiss()
}
}
}
}
}
struct CreatureAniDetail: View {
let creature : CreatureAni
@State private var isScaled = false
@State private var color = Color.red
@State private var shadowRadius : CGFloat = 0.5
@State private var angle = Angle(degrees: 0)
var body: some View {
VStack {
Text(creature.emoji)
.colorMultiply(color)
.shadow(color: color, radius: shadowRadius * 40)
.rotation3DEffect(Angle(degrees: 0), axis: (x: 5, y: 2, z: 1))
.scaleEffect(isScaled ? 1.5 : 1)
.animation(.spring(response: 0.5, dampingFraction: 0.5, blendDuration: 0.5), value: isScaled)
Button("缩放") {
isScaled.toggle()
}
ColorPicker("选取一种颜色", selection: $color)
HStack {
Text("阴影")
Slider(value: $shadowRadius)
}
}
.padding()
}
}
struct LabelGrid: View {
@State private var isAddingLabel = false
@State private var isEditing = false
@State private var selectedLabel: LabelItem?
private static let initialColumns = 3
@State private var numColumns = initialColumns
@State private var gridColumns = Array(repeating: GridItem(.flexible()), count: initialColumns)
@State private var labels = [
LabelItem(name: "tshirt"),
LabelItem(name: "eyes"),
LabelItem(name: "eyebrow"),
LabelItem(name: "moon.stars"),
]
var columnsText: String {
numColumns > 1 ? "\(numColumns) Columns" : "1 Column"
}
var body: some View {
VStack {
if isEditing {
Stepper(columnsText, value: $numColumns, in: 1...6, step: 1) { _ in
withAnimation { gridColumns = Array(repeating: GridItem(.flexible()), count: numColumns) }
}
.padding()
}
ScrollView {
LazyVGrid(columns: gridColumns) {
ForEach(labels) { label in
NavigationLink {
LabelDetail(label: label)
} label: {
Image(systemName: label.name)
.resizable()
.scaledToFit()
.symbolRenderingMode(.hierarchical)
.foregroundColor(.accentColor)
.padding()
}
.overlay(alignment: .topTrailing) {
if isEditing {
Button {
remove(label: label)
} label: {
Image(systemName: "xmark.square.fill")
.font(.title)
.symbolRenderingMode(.palette)
.foregroundStyle(.white, Color.red)
}
}
}
}
}
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
isAddingLabel = true
} label: {
Image(systemName: "plus")
}
}
ToolbarItem(placement: .navigationBarLeading) {
Button(isEditing ? "完成" : "编辑") {
withAnimation {
isEditing.toggle()
}
}
}
}
.sheet(isPresented: $isAddingLabel, onDismiss: addLabel) {
LabelPicker(label: $selectedLabel)
}
}
func addLabel() {
guard let item = selectedLabel else { return }
withAnimation {
labels.insert(item, at: 0)
}
}
func remove(label: LabelItem) {
guard let index = labels.firstIndex(of: label) else { return }
withAnimation {
_ = labels.remove(at: index)
}
}
}
struct LabelDetail: View {
var label: LabelItem
var body: some View {
VStack {
Text(label.name)
.font(.largeTitle)
Image(systemName: label.name)
.resizable()
.scaledToFit()
.symbolRenderingMode(.hierarchical)
.foregroundColor(.accentColor)
}
.padding()
}
}
struct LabelItem : Identifiable, Equatable {
var name : String
var id = UUID()
}
struct LabelPicker: View {
@Environment(\.presentationMode) var presentationMode
@Binding var label: LabelItem?
let columns = Array(repeating: GridItem(.flexible()), count: 4)
let pickableLabels = [
LabelItem(name: "waveform.path.ecg.rectangle.fill"),
LabelItem(name: "gyroscope"),
]
var body: some View {
ScrollView {
LazyVGrid(columns: columns) {
ForEach(pickableLabels) { label in
Button {
self.label = label
presentationMode.wrappedValue.dismiss()
} label: {
Image(systemName: label.name)
.resizable()
.scaledToFit()
.symbolRenderingMode(.hierarchical)
.foregroundColor(.accentColor)
.ignoresSafeArea(.container, edges: .bottom)
}
.padding()
.buttonStyle(.plain)
}
}
}
}
}
struct HalfCard: View {
var body: some View {
VStack {
Image(systemName: "crown.fill")
.font(.system(size: 80))
}
.overlay (alignment: .topLeading) {
VStack {
Image(systemName: "crown.fill")
.font(.body)
Text("Q")
.font(.largeTitle)
Image(systemName: "heart.fill")
.font(.title)
}
.padding()
}
.border(Color.blue)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.border(Color.green)
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)