竟赛萌芽书意项目经验
Swift
添加自定义字体
获取自定义字体的名称
1 2 3 4 5 6 7 8
| init() { UIFont.familyNames.forEach { familyName in print("Font Family: \(familyName)") UIFont.fontNames(forFamilyName: familyName).forEach { fontName in print("\t\(fontName)") } } }
|
使用:
修改Tabview颜色
更改Assets里面的AccentColor就行
TabItem自动填充选中图标的颜色颜色
参考:[SwiftUI Tabview:如何自定义标签栏 - swiftyplace](https://www.swiftyplace.com/blog/tabview-in-swiftui-styling-navigation-and-more#:~:text=How can I add icons to the tabs,Text(“Second View”).tabItem { Label(“Tab 2”%2C systemImage%3A “2.circle”) })
Assets里面Render As
属性改为Template Image
xcode 代码格式化 快捷键
在代码编辑器中,按 Command + A 选择整个文件。
然后按 Control + I 来格式化所有选定的代码。
swiftui中如何实现HStack内的内容为flex布局的justify-between的效果
Spacer()基本上用一个就够了
正确代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| HStack(spacing: 30) { Text("全部") .font(.custom("AlimamaShuHeiTi-Bold", size: 16)) .padding(.horizontal, 15) .padding(.vertical, 10) .foregroundColor(.white) .background(Color("accent-100")) .cornerRadius(12) Spacer() HStack { Text("流行") .foregroundColor(Color("accent-100")) Spacer() Text("最近") .foregroundColor(Color("accent-100")) Spacer() Text("推荐") .foregroundColor(Color("accent-100")) } } .frame(maxWidth: .infinity, maxHeight: 50)
|
错误代码:
每个 Spacer 会尽可能多地占据可用空间,导致 Text 视图之间的空间被过度分配,挤压 Text 视图,从而导致布局变形
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| struct ContentView: View { var body: some View { HStack { Text("全部") .font(.custom("AlimamaShuHeiTi-Bold", size: 16)) .padding(.horizontal, 15) .padding(.vertical, 10) .foregroundColor(.white) .background(Color("accent-100")) .cornerRadius(12) Spacer() HStack { Text("流行") .foregroundColor(Color("accent-100")) Spacer() Text("最近") .foregroundColor(Color("accent-100")) Spacer() Text("推荐") .foregroundColor(Color("accent-100")) } } .frame(maxWidth: .infinity, maxHeight: 50) } }
|
内部渐变效果
1 2 3 4 5 6 7 8 9 10 11
| Image("navigation_ai") .resizable() .cornerRadius(20) .overlay( LinearGradient( gradient: Gradient(colors: [.black.opacity(0.3), .black.opacity(0)]), startPoint: .bottomLeading, endPoint: UnitPoint(x: 0.5, y: 0.0) ) .cornerRadius(20) )
|
让中间的HStack宽度为占满剩下的屏幕
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| GeometryReader { geometry in HStack { HStack { Image("recommend_search") .resizable() .frame(width: 30, height: 30) Text("搜索") .font(.custom("Alibaba-PuHuiTi-R", size: 15)) .foregroundColor(Color("text-200")) Spacer() } .padding() .frame(width: geometry.size.width, height: 40) .background(Color("bg-200")) .cornerRadius(12) } .frame(height: 40) }
|
PencilKit
PKCanvasView
为画布
PKToolPicker
为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import SwiftUI import PencilKit
struct PracticeWithPencilView: View { @State private var canvasView = PKCanvasView() @State private var toolPicker = PKToolPicker()
var body: some View { ZStack { Image("Practice-02") .resizable() .scaledToFit() .frame(width: geometry.size.width * 0.97, height: geometry.size.height * 0.92) CanvasView(canvasView: $canvasView, toolPicker: $toolPicker) .frame(width: geometry.size.width * 0.97, height: geometry.size.height * 0.92) .background(Color.clear) } .frame(height: geometry.size.height * 0.86) } }
struct CanvasView: UIViewRepresentable { @Binding var canvasView: PKCanvasView @Binding var toolPicker: PKToolPicker func makeUIView(context: Context) -> PKCanvasView { canvasView.drawingPolicy = .anyInput canvasView.backgroundColor = .clear toolPicker.setVisible(true, forFirstResponder: canvasView) toolPicker.addObserver(canvasView) canvasView.becomeFirstResponder() return canvasView }
func updateUIView(_ uiView: PKCanvasView, context: Context) { } }
|
Git
回溯版本
参考:Git恢复之前版本的两种方法reset、revert(图文详解)_git回退到某个版本-CSDN博客
1 2
| git reset --hard c7c4e1d88e36f97a0c456a82cc2d5bd16a9633c2 git push -f
|
通过PencilKit获取单字字迹
依据时间顺序总结
好的,这里是按照时间顺序列出的各个方法的调用过程,并在每个事件点后附上对应的代码和详细解释:
1. 开始下第一笔(触摸开始)
当用户开始在画布上绘制时,touchesBegan
方法会被调用。
1 2 3 4 5 6 7
| override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) if let touch = touches.first { let point = touch.location(in: self) updateCurrentStrokeRect(with: point) } }
|
解释:
touchesBegan
是 UIResponder 类的一部分,UIResponder 是 iOS 事件响应链中的基类。UIView 以及其子类都继承自 UIResponder,因此可以重载这些方法来处理触摸事件
touchesBegan
方法通过touch.location(in: self)
记录了用户触摸的位置,并调用 updateCurrentStrokeRect(with:)
方法。
updateCurrentStrokeRect(with:)
方法更新 currentStrokeRect
,用来记录当前笔画的矩形区域。
updateCurrentStrokeRect(with point: CGPoint) 方法被调用
1 2 3 4 5 6 7
| func updateCurrentStrokeRect(with point: CGPoint) { if let currentRect = currentStrokeRect { currentStrokeRect = currentRect.union(CGRect(origin: point, size: .zero)) } else { currentStrokeRect = CGRect(origin: point, size: .zero) } }
|
解释:
- updateCurrentStrokeRect(with point: CGPoint) 方法用于更新当前笔画的矩形区域。
- currentStrokeRect 是一个可选的 CGRect 类型变量(包含了矩形的原点(即左上角的坐标)和尺寸(宽度和高度)),用于记录当前笔画的矩形区域。
- 如果 currentStrokeRect 已经存在,则使用 CGRect(origin: point, size: .zero) 创建一个新的零大小的矩形,并使用 union 方法将其与当前矩形区域合并,扩大 currentStrokeRect 以包含新的点。
- 如果 currentStrokeRect 不存在(即当前是第一笔),则使用 CGRect(origin: point, size: .zero) 创建一个新的零大小的矩形,并将其赋值给 currentStrokeRect。
2. 笔画移动(触摸移动)
当用户移动笔画时,touchesMoved
方法会被调用。
1 2 3 4 5 6 7
| override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesMoved(touches, with: event) if let touch = touches.first { let point = touch.location(in: self) updateCurrentStrokeRect(with: point) } }
|
解释:
touchesMoved
方法记录了用户移动的位置,并再次调用 updateCurrentStrokeRect(with:)
方法。
updateCurrentStrokeRect(with:)
方法会更新 currentStrokeRect
,扩大当前笔画的矩形区域以包含移动后的点。
3. 笔画结束(触摸结束)
当用户完成一个笔画时,touchesEnded
方法会被调用。
1 2 3 4 5 6 7 8
| override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesEnded(touches, with: event) if let touch = touches.first { let point = touch.location(in: self) updateCurrentStrokeRect(with: point) } resetTimer() }
|
解释:
touchesEnded
方法记录了用户触摸结束的位置,并调用 updateCurrentStrokeRect(with:)
方法。
resetTimer()
方法会启动一个2秒的定时器,定时器触发时会调用 handleTimer
方法。
4. 触发定时器(间隔2秒后)
如果用户在2秒内没有再进行新的绘制操作,定时器触发,handleTimer
方法会被调用。
1 2 3 4 5
| @objc func handleTimer() { guard let currentStrokeRect = currentStrokeRect else { return } saveCurrentDrawingAsImage(in: currentStrokeRect.insetBy(dx: -margin, dy: -margin)) self.currentStrokeRect = nil }
|
解释:
handleTimer
方法检查 currentStrokeRect
是否存在,如果存在,则调用 saveCurrentDrawingAsImage(in:)
方法保存当前笔画的图像。
saveCurrentDrawingAsImage(in:)
方法从画布中截取指定区域的图像,并调用 analyzeImage(_:)
方法进行图像分析。
5. 保存图像并分析
在保存图像时,saveCurrentDrawingAsImage(in:)
方法会被调用。
1 2 3 4
| func saveCurrentDrawingAsImage(in rect: CGRect) { let image = drawing.image(from: rect, scale: 1.0) analyzeImage(image) }
|
解释:
saveCurrentDrawingAsImage(in:)
方法从画布中截取指定矩形区域的图像,并调用 analyzeImage(_:)
方法进行图像分析。
6. 分析图像
最后,analyzeImage(_:)
方法会被调用。
1 2 3 4 5 6 7 8 9
| func analyzeImage(_ image: UIImage) { analyzeImageOnServer(image) }
func analyzeImageOnServer(_ image: UIImage) { print("开始分析图像...") }
|
解释:
analyzeImage(_:)
方法中调用 analyzeImageOnServer(_:)
方法将图像上传到服务器进行分析。
analyzeImageOnServer(_:)
方法中实际应用中可以添加图像上传逻辑。
总结起来,以下是完整的调用顺序:
- 开始下第一笔(触摸开始):
touchesBegan
-> updateCurrentStrokeRect(with:)
- 笔画移动(触摸移动):
touchesMoved
-> updateCurrentStrokeRect(with:)
- 笔画结束(触摸结束):
touchesEnded
-> updateCurrentStrokeRect(with:)
-> resetTimer()
- 触发定时器(间隔2秒后):
handleTimer
-> saveCurrentDrawingAsImage(in:)
-> analyzeImage(_:)
-> analyzeImageOnServer(_:)
通过这些步骤,代码实现了对单个字的捕捉、保存和分析。
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
| struct CanvasView: UIViewRepresentable { @Binding var canvasView: CustomPKCanvasView @Binding var toolPicker: PKToolPicker func makeUIView(context: Context) -> CustomPKCanvasView { canvasView.drawingPolicy = .anyInput canvasView.backgroundColor = .clear toolPicker.setVisible(true, forFirstResponder: canvasView) toolPicker.addObserver(canvasView) canvasView.becomeFirstResponder() canvasView.delegate = context.coordinator return canvasView } func updateUIView(_ uiView: CustomPKCanvasView, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, PKCanvasViewDelegate { var parent: CanvasView var timer: Timer? init(_ parent: CanvasView) { self.parent = parent } func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) { resetTimer() } func resetTimer() { timer?.invalidate() timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { _ in self.parent.canvasView.analyzeDrawing() } } } }
class CustomPKCanvasView: PKCanvasView { private let speechHelper = SpeechSynthesizerHelper() var drawingChangedTimer: Timer? var lastTouchPoint: CGPoint? var currentStrokeRect: CGRect? let margin: CGFloat = 10.0 func resetTimer() { drawingChangedTimer?.invalidate() drawingChangedTimer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(handleTimer), userInfo: nil, repeats: false) } @objc func handleTimer() { guard let currentStrokeRect = currentStrokeRect else { return } saveCurrentDrawingAsImage(in: currentStrokeRect.insetBy(dx: -margin, dy: -margin)) self.currentStrokeRect = nil } func analyzeDrawing() { guard let currentStrokeRect = currentStrokeRect else { return } saveCurrentDrawingAsImage(in: currentStrokeRect.insetBy(dx: -margin, dy: -margin)) } func saveCurrentDrawingAsImage(in rect: CGRect) { let image = drawing.image(from: rect, scale: 1.0) analyzeImage(image) } func updateCurrentStrokeRect(with point: CGPoint) { if let currentRect = currentStrokeRect { currentStrokeRect = currentRect.union(CGRect(origin: point, size: .zero)) } else { currentStrokeRect = CGRect(origin: point, size: .zero) } } func analyzeImage(_ image: UIImage) { analyzeImageOnServer(image) } func analyzeImageOnServer(_ image: UIImage) { print("开始分析图像...") DispatchQueue.main.async { self.speechHelper.speak(text: "开始分析您的笔迹,请耐心等待") } guard let imageData = image.jpegData(compressionQuality: 0.8) else { print("无法转换图像为JPEG数据") return } let url = URL(string: "https://api.gptapi.us/v1/chat/completions")! var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("Bearer sk-BhcZcQ4KbJW8wmgX96F33d91B14c41CaAf23C4F15d1483Ec", forHTTPHeaderField: "Authorization") let base64Image = imageData.base64EncodedString() let textPart: [String: Any] = [ "type": "text", "text": "针对我写的这个字的字迹给出建议,要求字数不超过50字" ] let imagePart: [String: Any] = [ "type": "image_url", "image_url": [ "url": "data:image/jpeg;base64,\(base64Image)", "detail": "low" ] ] let messageContent: [Any] = [textPart, imagePart] let message: [String: Any] = ["role": "user", "content": messageContent] let jsonBody: [String: Any] = [ "model": "gpt-4-turbo", "messages": [message], "max_tokens": 300 ] guard let jsonData = try? JSONSerialization.data(withJSONObject: jsonBody, options: []) else { print("无法构建JSON体") return } request.httpBody = jsonData let task = URLSession.shared.dataTask(with: request) { data, response, error in if let error = error { print("请求错误: \(error)") return } guard let data = data else { print("未收到数据") return } if let responseJSON = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], let choices = responseJSON["choices"] as? [[String: Any]], let message = choices.first?["message"] as? [String: Any], let content = message["content"] as? String { DispatchQueue.main.async { self.speechHelper.speak(text: content) } print("识别结果: \(content)") } else { print("无法解析响应数据") } } task.resume() } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) if let touch = touches.first { let point = touch.location(in: self) updateCurrentStrokeRect(with: point) } } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesMoved(touches, with: event) if let touch = touches.first { let point = touch.location(in: self) updateCurrentStrokeRect(with: point) } } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesEnded(touches, with: event) if let touch = touches.first { let point = touch.location(in: self) updateCurrentStrokeRect(with: point) } resetTimer() } }
|