SwiftUI- 服务器未接收到 base64 字符串
SwiftUI- server not receiving base64 string
我想做的是拍照,将其编码为 base64,然后将其通过字典发送到我的服务器。当我发送字典时,有时它会使用 base64 发送,有时即使我不做任何更改也会在没有它的情况下发送。
这是相机视图的代码
struct CameraView: View {
@StateObject var camera = CameraModel()
var body: some View {
ZStack {
CameraPreview(camera: camera)
.ignoresSafeArea(.all, edges: .all)
VStack{
if camera.isTaken{
HStack {
Spacer()
Button(action: camera.retake, label: {
Image(systemName: "arrow.triangle.2.circlepath.camera")
.foregroundColor(.black)
.padding()
.background(Color.white)
.clipShape(Circle())
})
.padding(.trailing, 10)
}
}
Spacer()
HStack{
if camera.isTaken{
Button(action: camera.sendPicData , label: {
Text("Save")
.foregroundColor(.black)
.fontWeight(.semibold)
.padding(.vertical, 10)
.padding(.horizontal, 10)
.background(Color.white)
.clipShape(Capsule())
})
.padding(.leading)
Spacer()
}
else{
Button(action: camera.takePic, label: {
ZStack{
Circle()
.fill(Color.white)
.frame(width: 65, height: 65)
Circle()
.stroke(Color.white, lineWidth: 2)
.frame(width: 75, height: 75)
}
})
}
}
.frame(height: 75)
}
}
.onAppear(perform: {
camera.Check()
})
}
}
struct CameraPreview: UIViewRepresentable{
@ObservedObject var camera: CameraModel
func makeUIView(context: Context) -> UIView {
let view = UIView(frame: UIScreen.main.bounds)
camera.preview = AVCaptureVideoPreviewLayer(session: camera.session)
camera.preview.frame = view.frame
camera.preview.videoGravity = .resizeAspectFill
view.layer.addSublayer(camera.preview)
camera.session.startRunning()
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
以及拍照并发送到服务器的代码
class CameraModel: NSObject, ObservableObject, AVCapturePhotoCaptureDelegate{
@Published var isTaken = false
@Published var alert = false
@Published var sendPic = false
@Published var session = AVCaptureSession()
@Published var output = AVCapturePhotoOutput()
@Published var preview: AVCaptureVideoPreviewLayer!
@Published var picData = Data(count: 0)
func Check(){
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
setUp()
return
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { (status) in
if status{
self.setUp()
}
}
case .denied:
self.alert.toggle()
return
default:
return
}
}
func setUp(){
do{
self.session.beginConfiguration()
let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
let input = try AVCaptureDeviceInput(device: device!)
if self.session.canAddInput(input){
self.session.addInput(input)
}
if self.session.canAddOutput(self.output){
self.session.addOutput(self.output)
}
self.session.commitConfiguration()
}
catch{
print(error.localizedDescription)
}
}
func takePic(){
DispatchQueue.global(qos: .background).async {
self.output.capturePhoto(with: AVCapturePhotoSettings(), delegate: self)
self.session.stopRunning()
DispatchQueue.main.async {
withAnimation{self.isTaken.toggle()}
}
print("pic taken...")
}
}
func retake(){
DispatchQueue.global(qos: .background).async {
self.session.startRunning()
DispatchQueue.main.async {
withAnimation{self.isTaken.toggle()}
}
}
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
if error != nil{
return
}
guard let imageData = photo.fileDataRepresentation() else{return}
self.picData = imageData
}
func sendPicData(){
let image = UIImage(data: self.picData)
let imageData: Data = image?.jpegData(compressionQuality: 0.1) ?? Data()
let imageString: String = imageData.base64EncodedString()
let dictionary:[String:String] = ["Dot":imageString]
if let theJSONData = try? JSONSerialization.data(withJSONObject: dictionary, options: [.prettyPrinted]) {
guard let sendURL = URL(string:"http://toServer") else {
print("invalid URL")
return
}
var request = URLRequest(url: sendURL)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.httpMethod = "POST"
request.httpBody = theJSONData
URLSession.shared.dataTask(with: request) {data, response, error in
guard data != nil else {
print ("PUT Failed: \(error?.localizedDescription ?? "Unknown Error")")
return
}
}.resume()
print(dictionary)
}
}
}
我不知道为什么这么不一致。这对我来说没有任何意义,如果它一次有效,为什么不是每次都有效?请帮忙
问题是委托函数 func photoOutput()
并没有在每次拍照时都被调用。我发现在 func takePic()
中,如果我将 Timer
放在 self.session.stopRunning()
上,它会解决问题并每次都调用委托函数,但它也会导致另一个问题,即停止调用 self.isTaken.toggle()
。为了解决这个问题,我把 Timer
放在 DispatchQueue.main.async
中,像这样
func takePic(){
DispatchQueue.global(qos: .background).async {
self.output.capturePhoto(with: AVCapturePhotoSettings(), delegate: self)
DispatchQueue.main.async {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { (timer) in
self.session.stopRunning()
}
}
DispatchQueue.main.async {
withAnimation{self.isTaken.toggle()}
}
print("pic taken...")
}
}
我不确定这是否是解决此问题的最佳方法,但我见过很多其他人遇到同样类似的问题,并且使用此解决方案每次都会调用委托函数。
你为什么要切换 isTaken 之前(它是)。
我认为这里的问题是您允许在拍照线程完成之前保存照片(显示按钮)。
func takePic(){
DispatchQueue.global(qos: .background).async {
self.output.capturePhoto(with: AVCapturePhotoSettings(), delegate: self)
self.session.stopRunning()
//DispatchQueue.main.async {
// withAnimation{self.isTaken.toggle()} // << IT ISNT TAKEN YET
//}
//print("pic taken...") // << AGAIN IT ISNT TAKEN YET
}
}
当 takePic 线程完成时,它调用委托(回调):
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
// NOW ITS TAKEN BUT MAY HAVE ERRORS
if error != nil{
return // MAYBE THIS NEED SOME BETTER FEEDBACK FOR THE USER
}
guard let imageData = photo.fileDataRepresentation() else{return} // USER FEEDBACK??
self.picData = imageData
// NOW ITS TAKEN
// I'm assuming this is running on the main thread.
withAnimation{self.isTaken.toggle()} // SO TOGGLE isTaken
}
我在没有测试的情况下写这篇文章,但我认为你混淆了回调的工作原理。
https://en.wikipedia.org/wiki/Callback_%28computer_programming%29
这里的 func photoOutput 是回调(除非我在这里遗漏了一些东西,因为我自己对 SwiftUI 还很陌生)。
我想做的是拍照,将其编码为 base64,然后将其通过字典发送到我的服务器。当我发送字典时,有时它会使用 base64 发送,有时即使我不做任何更改也会在没有它的情况下发送。 这是相机视图的代码
struct CameraView: View {
@StateObject var camera = CameraModel()
var body: some View {
ZStack {
CameraPreview(camera: camera)
.ignoresSafeArea(.all, edges: .all)
VStack{
if camera.isTaken{
HStack {
Spacer()
Button(action: camera.retake, label: {
Image(systemName: "arrow.triangle.2.circlepath.camera")
.foregroundColor(.black)
.padding()
.background(Color.white)
.clipShape(Circle())
})
.padding(.trailing, 10)
}
}
Spacer()
HStack{
if camera.isTaken{
Button(action: camera.sendPicData , label: {
Text("Save")
.foregroundColor(.black)
.fontWeight(.semibold)
.padding(.vertical, 10)
.padding(.horizontal, 10)
.background(Color.white)
.clipShape(Capsule())
})
.padding(.leading)
Spacer()
}
else{
Button(action: camera.takePic, label: {
ZStack{
Circle()
.fill(Color.white)
.frame(width: 65, height: 65)
Circle()
.stroke(Color.white, lineWidth: 2)
.frame(width: 75, height: 75)
}
})
}
}
.frame(height: 75)
}
}
.onAppear(perform: {
camera.Check()
})
}
}
struct CameraPreview: UIViewRepresentable{
@ObservedObject var camera: CameraModel
func makeUIView(context: Context) -> UIView {
let view = UIView(frame: UIScreen.main.bounds)
camera.preview = AVCaptureVideoPreviewLayer(session: camera.session)
camera.preview.frame = view.frame
camera.preview.videoGravity = .resizeAspectFill
view.layer.addSublayer(camera.preview)
camera.session.startRunning()
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
以及拍照并发送到服务器的代码
class CameraModel: NSObject, ObservableObject, AVCapturePhotoCaptureDelegate{
@Published var isTaken = false
@Published var alert = false
@Published var sendPic = false
@Published var session = AVCaptureSession()
@Published var output = AVCapturePhotoOutput()
@Published var preview: AVCaptureVideoPreviewLayer!
@Published var picData = Data(count: 0)
func Check(){
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
setUp()
return
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { (status) in
if status{
self.setUp()
}
}
case .denied:
self.alert.toggle()
return
default:
return
}
}
func setUp(){
do{
self.session.beginConfiguration()
let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
let input = try AVCaptureDeviceInput(device: device!)
if self.session.canAddInput(input){
self.session.addInput(input)
}
if self.session.canAddOutput(self.output){
self.session.addOutput(self.output)
}
self.session.commitConfiguration()
}
catch{
print(error.localizedDescription)
}
}
func takePic(){
DispatchQueue.global(qos: .background).async {
self.output.capturePhoto(with: AVCapturePhotoSettings(), delegate: self)
self.session.stopRunning()
DispatchQueue.main.async {
withAnimation{self.isTaken.toggle()}
}
print("pic taken...")
}
}
func retake(){
DispatchQueue.global(qos: .background).async {
self.session.startRunning()
DispatchQueue.main.async {
withAnimation{self.isTaken.toggle()}
}
}
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
if error != nil{
return
}
guard let imageData = photo.fileDataRepresentation() else{return}
self.picData = imageData
}
func sendPicData(){
let image = UIImage(data: self.picData)
let imageData: Data = image?.jpegData(compressionQuality: 0.1) ?? Data()
let imageString: String = imageData.base64EncodedString()
let dictionary:[String:String] = ["Dot":imageString]
if let theJSONData = try? JSONSerialization.data(withJSONObject: dictionary, options: [.prettyPrinted]) {
guard let sendURL = URL(string:"http://toServer") else {
print("invalid URL")
return
}
var request = URLRequest(url: sendURL)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.httpMethod = "POST"
request.httpBody = theJSONData
URLSession.shared.dataTask(with: request) {data, response, error in
guard data != nil else {
print ("PUT Failed: \(error?.localizedDescription ?? "Unknown Error")")
return
}
}.resume()
print(dictionary)
}
}
}
我不知道为什么这么不一致。这对我来说没有任何意义,如果它一次有效,为什么不是每次都有效?请帮忙
问题是委托函数 func photoOutput()
并没有在每次拍照时都被调用。我发现在 func takePic()
中,如果我将 Timer
放在 self.session.stopRunning()
上,它会解决问题并每次都调用委托函数,但它也会导致另一个问题,即停止调用 self.isTaken.toggle()
。为了解决这个问题,我把 Timer
放在 DispatchQueue.main.async
中,像这样
func takePic(){
DispatchQueue.global(qos: .background).async {
self.output.capturePhoto(with: AVCapturePhotoSettings(), delegate: self)
DispatchQueue.main.async {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { (timer) in
self.session.stopRunning()
}
}
DispatchQueue.main.async {
withAnimation{self.isTaken.toggle()}
}
print("pic taken...")
}
}
我不确定这是否是解决此问题的最佳方法,但我见过很多其他人遇到同样类似的问题,并且使用此解决方案每次都会调用委托函数。
你为什么要切换 isTaken 之前(它是)。
我认为这里的问题是您允许在拍照线程完成之前保存照片(显示按钮)。
func takePic(){
DispatchQueue.global(qos: .background).async {
self.output.capturePhoto(with: AVCapturePhotoSettings(), delegate: self)
self.session.stopRunning()
//DispatchQueue.main.async {
// withAnimation{self.isTaken.toggle()} // << IT ISNT TAKEN YET
//}
//print("pic taken...") // << AGAIN IT ISNT TAKEN YET
}
}
当 takePic 线程完成时,它调用委托(回调):
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
// NOW ITS TAKEN BUT MAY HAVE ERRORS
if error != nil{
return // MAYBE THIS NEED SOME BETTER FEEDBACK FOR THE USER
}
guard let imageData = photo.fileDataRepresentation() else{return} // USER FEEDBACK??
self.picData = imageData
// NOW ITS TAKEN
// I'm assuming this is running on the main thread.
withAnimation{self.isTaken.toggle()} // SO TOGGLE isTaken
}
我在没有测试的情况下写这篇文章,但我认为你混淆了回调的工作原理。
https://en.wikipedia.org/wiki/Callback_%28computer_programming%29
这里的 func photoOutput 是回调(除非我在这里遗漏了一些东西,因为我自己对 SwiftUI 还很陌生)。