2

I send the text to the server with picture via POST. The text come correctly, but the picture is not complete. 10 percent of the picture is displayed correctly, and other is just as gray background. Swift converts the image file to text using base64EncodedString().

It seems that Swift performs the conversion with an error, or the server does not fully receive the data. But I have increase the limit for POST and it doesn't help. I also changed the image compression values with compressionQuality, it did not help.

Code from view file:

Button(action: {
    self.checkBoxStatus = false

    let uiImage: UIImage = self.selectedImage.asUIImage()
    let imageData: Data = uiImage.jpegData(compressionQuality: 0.9) ?? Data()
    let imageStr: String = imageData.base64EncodedString()

    let shareHelper = ShareHelper(message: validateForm.content, user: validateForm.user, email: validateForm.email, media: imageStr)
    shareHelper.RequestPost { (dataString) in
        self.checkRequestStatus = true
        validateForm.content = ""
        validateForm.user = ""
        validateForm.email = ""
        validateForm.media = ""
        self.selectedImage = Image("")
    }
}, label: {
    Text("Send")
})

How to fix it?

P.S.:

POST request code:

import Foundation

class ShareHelper {

    var dataString: String = ""
    var newsMessage: String
    var newsUser: String
    var newsEmail: String
    var newsMedia: String
    let newsAPI: String = "https://example.com/api/shareNews"

    init(message: String, user: String, email: String, media: String) {
        self.newsMessage = message
        self.newsUser = user
        self.newsEmail = email
        self.newsMedia = media
    }

    func RequestPost(completion: @escaping((String) -> Void)) {
        let url = URL(string: self.newsAPI)
        guard let requestUrl = url else { fatalError() }
        var request = URLRequest(url: requestUrl)
        request.httpMethod = "POST"
        let postString = "message=\(self.newsMessage)&user=\(self.newsUser)&email=\(self.newsEmail)&media=\(self.newsMedia)"
        request.httpBody = postString.data(using: String.Encoding.utf8)
        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
            if error != nil {
                return
            }
            if let data = data, let dataString = String(data: data, encoding: .utf8) {
                DispatchQueue.main.async {
                    self.dataString = dataString
                    completion(dataString)
                }
            }
        }
        task.resume()
    }

}
LeXxyIT
  • 204
  • 2
  • 15
  • What's the size of `imageStr`? Do you the the Content-Length in your request? Do you get the correct size on your server? Are you using a "basic post", or using a multipart/url form data? – Larme Apr 09 '21 at 09:21
  • I added the POST request code to my question. – LeXxyIT Apr 09 '21 at 09:26
  • How do you initialize the class? does `newsMessage` contain the string of the UIImage?! – Mahdi BM Apr 12 '21 at 12:49
  • I have update my question and add code with initializing class. newsMessage - is string. – LeXxyIT Apr 12 '21 at 13:20
  • `ShareHelper(message: validateForm.content, user: validateForm.user, email: validateForm.email, media: imageStr)` – LeXxyIT Apr 12 '21 at 13:34
  • Maybe the base64 character set conflicts with the url-encoding. So if the base64 representation of the image contains a / or some other special character part way through, that may make the server think the image stopped right there. – Cenk Bilgen Apr 13 '21 at 15:55

3 Answers3

1

I have seen some solutions to your problem in this thread How to upload images to a server in iOS with Swift?

In this thread there are also answers which demonstrate how to upload an image via POST method to a server.

Mr Spring
  • 533
  • 8
  • 17
  • Yes of course. I have searched many posts with the same questions. But they have solutions for `Storyboard`, and I need for `SwiftUI`. And I'm not only sending the image. I have a lot of text data to send besides the picture. And all this needs to be sent at once at the push of a button. – LeXxyIT Apr 13 '21 at 13:41
  • The link in your answer is very old. That question was asked 6 years ago. – LeXxyIT Apr 13 '21 at 13:42
  • LeXxy, you are right the post is old but the answers are still relevant. also i don't understand what does either Storyboard or SwiftUI are relevant to these solutions, you are talking about posting an image representation data to a server – Mr Spring Apr 13 '21 at 13:57
1

The best practice is provide an upload api for only uploading images. You can use multipart POST to make it done well. Then get the response with uploaded image's ID, and add it to your shareNews api request.

Server-side should manage images by id.

For your current code, it works well I guess, try to ask backend developer how they decode your base64-ed data.

Quang Hà
  • 4,613
  • 3
  • 24
  • 40
1

You can use the Combine framework using a SerialQueue and send your image separately than the data related to your news.

So this is the SwiftUI view where the button is held. You will notice that I have introduced a view model to avoid any logic inside the view.

import SwiftUI

struct ContentView: View {

  /// Used to separate the logic from the view.
  @ObservedObject var viewModel = ContentViewModel()

  var body: some View {
    Button(action: { viewModel.sendNewsData() }) {
      Text("Send")
    }
  }
}

So this is the view model itself, where the logic of sending your data happens. You will have to handle sending the news data separately. If you need some help to use Combine to do so, just ask a new question on StackOverflow.

import Combine
import SwiftUI

final class ContentViewModel: ObservableObject {

  let networkRequestManager = NetworkRequestManager()

  let image = UIImage(named: "MySpecialImage")!  // The image you want to send

  var cancellables = Set<AnyCancellable>()

  /// Send the news data and the image in one function
  /// to be used in your SwiftUI view.
  func sendNewsData() {
    postImageData(of: image)
    postNewsData()
  }

  /// Send the image on its own method.
  ///
  /// The data encoded string printed is related
  /// to your image that comes back from the api.
  func postImageData(of image: UIImage) {
    networkRequestManager
      .sendImage(image)
      .sink(
        receiveCompletion: { completion in
          print(completion) },
        receiveValue: { data in
          print(data) }) // your image
      .store(in: &cancellables)
  }

  func postNewsData() {
    // Just post your news data without the image
    // for it to be sent separately.
  }
}

So here is the NetworkRequestManager class that handles sending the image encoded to a String to your api endpoint. Just change the url as you need. Don't forget to change the key associated to the image with the one related to in your api. If you need a solution to fetch back the image using Combine and a Cache system, just ask a new question on StackOverflow.

import Combine
import SwiftUI

final class NetworkRequestManager {

  /// Send the image in a serial queue to not obstruct the main queue.
  let imageSerialQueue = DispatchQueue(label: "imageSerialQueue")

  /// This is where you will encode your image data to send it to your api.
  func sendImage(_ image: UIImage) -> AnyPublisher<String, Error> {

    let url = URL(string: "https://example.com/api/shareNews")!
    let body = setupBody(with: image)
    let urlRequest = setupURLRequest(url: url, body: body)

    return URLSession.shared
      .dataTaskPublisher(for: urlRequest)
      .subscribe(on: imageSerialQueue)
      .map { $0.data }
      .encode(encoder: JSONEncoder())
      .decode(type: String.self, decoder: JSONDecoder())
      .eraseToAnyPublisher()
  }

  /// The body related to your endpoint.
  ///
  /// Make sure that the dictionary key matches the one in your api.
  func setupBody(with image: UIImage) -> [String: Any] {
    let jpegData = image.jpegData(compressionQuality: 1)
    return ["newsMedia": jpegData?.base64EncodedString() as Any]
  }

  /// Setup the url request to send a POST method with your image
  /// in a json format.
  func setupURLRequest(url: URL,
                       body: [String: Any]) -> URLRequest {

    var urlRequest = URLRequest(url: url)
    urlRequest.allowsConstrainedNetworkAccess = true
    urlRequest.httpMethod = "POST"
    urlRequest.setValue("application/json",
                        forHTTPHeaderField: "Content-Type")
    urlRequest.httpBody = try? JSONSerialization.data(withJSONObject: body)
    return urlRequest
  }
}
Roland Lariotte
  • 2,606
  • 1
  • 16
  • 40