SwiftUI кажется классным, но некоторые вещи кажутся мне сложными. Несмотря на это, я бы лучше понял, как лучше сделать что-то по-SwiftUI, чем оборачивать контроллеры pre-swiftui и делать что-то по-старому. Итак, позвольте мне начать с простой проблемы - показа веб-изображения по URL. Есть решения, но их не так просто найти и не так просто понять.
У меня есть решение, и я хотел бы получить некоторую обратную связь. Ниже приведен пример того, что я хотел бы сделать (изображения из Open Images).
struct ContentView: View {
@State var imagePath: String = "https://farm2.staticflickr.com/440/19711210125_6c12414d8f_o.jpg"
var body: some View {
WebImage(imagePath: $imagePath).scaledToFit()
}
}
Мое решение заключается в размещении небольшого кода в верхней части корпуса, чтобы начать загрузку изображения. Путь к изображению имеет оболочку свойства @Binding - если он изменится, я хочу обновить свой вид. Существует также переменная myimage с оберткой свойства @State - когда она устанавливается, я также хочу обновить свое представление. Если все идет хорошо с загрузкой изображения, myimage будет установлен, и изображение отобразится. Первоначальная проблема заключается в том, что изменение состояния в теле приведет к тому, что представление будет признано недействительным и будет запускать еще одну загрузку до бесконечности. Решение кажется простым (код ниже). Просто проверьте imagePath и посмотрите, изменился ли он с момента последней загрузки. Обратите внимание, что при загрузке я сразу установил prev, что вызывает другое выполнение bodyУсловие приводит к тому, что изменение состояния игнорируется.
Я где-то читал, что @State проверяет равенство и игнорирует наборы, если значение не изменяется. Этот вид проверки на равенство не удастся для UIImage. Я ожидаю три вызова тела: начальный вызов, вызов, когда я устанавливаю prev, и вызов, когда я устанавливаю изображение. Я полагаю, я мог бы добавить изменяемое значение для prev (то есть, простой класс) и избежать второго вызова.
Обратите внимание, что загрузка веб-контента могла быть выполнена с использованием расширения и замыканий, но это другая проблема. Это привело бы к сокращению WebImage до нескольких строк кода.
Итак, есть ли лучший способ выполнить эту задачу?
//
// ContentView.swift
// Learn
//
// Created by John Morris on 11/26/19.
// Copyright © 2019 John Morris. All rights reserved.
//
import SwiftUI
struct WebImage: View {
@Binding var imagePath: String?
@State var prev: String?
@State var myimage: UIImage?
@State var message: String?
var body: some View {
if imagePath != prev {
self.downloadImage(from: imagePath)
}
return VStack {
myimage.map({Image(uiImage: $0).resizable()})
message.map({Text("\($0)")})
}
}
init?(imagePath: Binding<String?>) {
guard imagePath.wrappedValue != nil else {
return nil
}
self._imagePath = imagePath
guard let _ = URL(string: self.imagePath!) else {
return nil
}
}
func getData(from url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
}
func downloadImage(from imagePath: String?) {
DispatchQueue.main.async() {
self.prev = imagePath
}
guard let imagePath = imagePath, let url = URL(string: imagePath) else {
self.message = "Image path is not URL"
return
}
getData(from: url) { data, response, error in
if let error = error {
self.message = error.localizedDescription
return
}
guard let httpResponse = response as? HTTPURLResponse else {
self.message = "No Response"
return
}
guard (200...299).contains(httpResponse.statusCode) else {
if httpResponse.statusCode == 404 {
self.message = "Page, \(url.absoluteURL), not found"
} else {
self.message = "HTTP Status Code \(httpResponse.statusCode)"
}
return
}
guard let mimeType = httpResponse.mimeType else {
self.message = "No mimetype"
return
}
guard mimeType == "image/jpeg" else {
self.message = "Wrong mimetype"
return
}
print(response.debugDescription)
guard let data = data else {
self.message = "No Data"
return
}
if let image = UIImage(data: data) {
DispatchQueue.main.async() {
self.myimage = image
}
}
}
}
}
struct ContentView: View {
var images = ["https://c1.staticflickr.com/3/2260/5744476392_5d025d6a6a_o.jpg",
"https://c1.staticflickr.com/9/8521/8685165984_e0fcc1dc07_o.jpg",
"https://farm1.staticflickr.com/204/507064030_0d0cbc850c_o.jpg",
"https://farm2.staticflickr.com/440/19711210125_6c12414d8f_o.jpg"
]
@State var imageURL: String?
@State var count = 0
var body: some View {
VStack {
WebImage(imagePath: $imageURL).scaledToFit()
Button(action: {
self.imageURL = self.images[self.count]
self.count += 1
if self.count >= self.images.count {
self.count = 0
}
}) {
Text("Next")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}