Я довольно приличный разработчик Objective C, и сейчас я изучаю Swift (который мне кажется довольно сложным не только из-за новых концепций, таких как дополнительные опции, но и потому, что Swift постоянно развивается, а большая часть доступных руководств - сильно устарел).
В настоящее время я пытаюсь проанализировать JSON с URL-адреса в NSDictionary, а затем использовать одно из его значений для отображения изображения (которое также является URL-адресом). Что-то вроде этого:
URL -> NSDictionary -> инициализировать UIImage из URL -> отобразить UIImage в UIImageView
Это довольно просто в Objective C (и может быть даже более короткий ответ):
NSURL *url = [NSURL URLWithString:@"https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY"];
NSData *apodData = [NSData dataWithContentsOfURL:url];
NSDictionary *apodDict = [NSJSONSerialization JSONObjectWithData:apodData options:0 error:nil];
Приведенный выше фрагмент кода возвращает мне стандартный NSDictionary, в котором я могу обратиться к ключу «url», чтобы получить адрес изображения, которое я хочу отобразить:
"url": "https://apod.nasa.gov/apod/image/1811/hillpan_apollo15_4000.jpg"
Затем я конвертирую его в UIImage и передаю его UIImageView:
NSURL *imageURL = [NSURL URLWithString: [apodDict objectForKey:@"url"]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *apodImage = [UIImage imageWithData:imageData];
UIImageView *apodView = [[UIImageView alloc] initWithImage: apodImage];
Теперь я в основном пытаюсь воспроизвести приведенный выше код Objective C в Swift, но постоянно натыкаюсь на стены. Я пробовал несколько руководств (один из которых фактически делал то же самое: отображал изображение НАСА), а также нашел несколько ответов о переполнении стека, но ни один из них не помог, потому что они либо устарели, либо работают не так, как мне нужно.
Итак, я хотел бы попросить сообщество предоставить код Swift 4 для следующих проблем:
1. Convert data from url into a Dictionary
2. Use key:value pair from dict to get url to display an image
Если это еще не слишком много, я также хотел бы попросить подробные описания вместе с кодом, потому что я хотел бы, чтобы ответ был единственным исчерпывающим «учебником» для этой задачи, который, как мне кажется, в настоящее время нигде недоступен.
Спасибо!





Во-первых, я почти уверен, что через полгода вы найдете Objective-C очень сложным и трудным. ?
Во-вторых, даже ваш код ObjC не рекомендуется. Не загружайте данные с удаленного URL-адреса синхронным методом Data(contentsOf. Независимо от языка используйте асинхронный способ, например (NS)URLSession.
И не используйте в Swift типы коллекций Foundation NSArray и NSDictionary. По сути, вообще не используйте классы NS..., если есть собственный аналог Swift.
В Swift 4 вы можете легко декодировать JSON с помощью протокола Decodable непосредственно в структуру (Swift),
.
строка URL может быть даже декодирована как URL.
Создать структуру
struct Item: Decodable {
// let copyright, date, explanation: String
// let hdurl: String
// let mediaType, serviceVersion, title: String
let url: URL
}
Раскомментируйте строки, если вам нужно больше, чем URL.
И загрузите данные двумя задачами с данными.
let url = URL(string: "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY")!
let task = URLSession.shared.dataTask(with: url) { (data, _, error) in
if let error = error { print(error); return }
do {
let decoder = JSONDecoder()
// this line is only needed if all JSON keys are decoded
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Item.self, from: data!)
let imageTask = URLSession.shared.dataTask(with: result.url) { (imageData, _, imageError) in
if let imageError = imageError { print(imageError); return }
DispatchQueue.main.async {
let apodImage = UIImage(data: imageData!)
let apodView = UIImageView(image: apodImage)
// do something with the image view
}
}
imageTask.resume()
} catch { print(error) }
}
task.resume()
Вадиан, кажется, ты знаешь, что делаешь, спасибо за ответ! Однако я хотел бы попросить вас внести несколько изменений, прежде чем я приму это: 1. Не могли бы вы изменить свою основную функцию, чтобы она просто возвращала объект NSDictionary (из-за чистого кода, поэтому каждая функция выполняет только одну задачу). 2. Не могли бы вы прокомментировать свой код, чтобы я знал, что делает каждая строка и почему / как? Это важно для понимания вашего ответа, а не просто для копирования / вставки. Я знаю, что это много, но я думаю, что это необходимо, чтобы заполнить пустоту, в которой сейчас не хватает руководств для этой задачи.
Как я уже сказал, не используйте NSDictionary в Swift. Структура быстрее более безопасна и эффективна. Асинхронная задача ничего не возвращение, поэтому вам понадобится (или два) обработчика завершения. Здесь на SO есть бесчисленное множество вопросов о URLSession и обработчиках завершения.
Извините, это не NSDictionary, struct. Ты прав! Но важнее то, что я до сих пор не понимаю ваш код. :( На данный момент все, что я могу сделать, это скопировать и вставить его, а это не то, что я ищу; я хочу понять, что я делаю. Я также считаю, что важно придерживаться чистых правил кодирования и позволить функциям обрабатывать конкретные задачи, такие как декодирование JSON в объект данных, совместимый с языком. Позже это также поможет мне лучше понять ваше решение, поскольку оно будет разбито на более мелкие задачи.
вам нужно преобразовать URL-адрес в строку и данные для добавления в просмотр изображений
let imageURL:URL=URL(string: YourImageURL)!
let data=NSData(contentsOf: imageURL)
Yourimage.image=UIImage(data: data! as Data)
Поскольку загрузка изображений является тривиальной и в то же время задачей, которую можно реализовать разными способами, я бы порекомендовал вам не «изобретать колесо» и взглянуть на библиотеку загрузки изображений, такую как Ядерная бомба, поскольку она уже охватывает большинство кейсов, которые могут вам понадобиться в процессе разработки.
Это позволяет вам загружать и показывать изображение асинхронно в вашем представлении, используя простой api:
Nuke.loadImage(with: url, into: imageView)
А также при необходимости - указать как, изображение должно быть загружено и представлено:
let options = ImageLoadingOptions(
placeholder: UIImage(named: "placeholder"),
failureImage: UIImage(named: "failure_image"),
contentModes: .init(
success: .scaleAspectFill,
failure: .center,
placeholder: .center
)
)
Nuke.loadImage(with: url, options: options, into: imageView)
Сначала добавьте модуль в Podfile капсула 'Аламофайр', под "AlamofireImage" вы можете проверить эту ссылку для установки pods => https://cocoapods.org/pods/AlamofireImage
// Используйте эту функцию для загрузки изображения из URL-адреса в просмотре изображений
imageView.af_setImage(
withURL: url,
placeholderImage: placeholderImage //its optional if you want to add placeholder
)
Проверьте эту ссылку для метода alamofireImage https://github.com/Alamofire/AlamofireImage/blob/master/Documentation/AlamofireImage%203.0%20Migration%20Guide.md
Создайте расширение UIIimageView и следующий код
extension UIImageView {
public func imageFromServerURL(urlString: String) {
self.image = nil
let urlStringNew = urlString.replacingOccurrences(of: " ", with: "%20")
URLSession.shared.dataTask(with: NSURL(string: urlStringNew)! as URL, completionHandler: { (data, response, error) -> Void in
if error != nil {
print(error as Any)
return
}
DispatchQueue.main.async(execute: { () -> Void in
let image = UIImage(data: data!)
self.image = image
})
}).resume()
}}
а также
self.UploadedImageView.imageFromServerURL(urlString: imageURLStirng!)
Я только что расширил ответ Вадиана, разделил некоторые проблемы, чтобы четко понять основы. Его ответа должно хватить.
Во-первых, вы должны построить свою структуру. Это будет представлять структуру JSON, полученную вами из веб-службы.
struct Item: Codable {
let url, hdurl : URL,
let copyright, explanation, media_type, service_version, title : String
}
Тогда заставьте вас запросить методы. Обычно я создаю для него отдельный файл. Теперь Вадиан упомянул об обработчиках завершения. Они представлены бегущими замыканиями. Здесь closure () -> передается обеим функциям и вызывается с декодированными данными в качестве аргумента.
struct RequestCtrl {
func fetchItem(completion: @escaping (Item?)->Void) {
let url = URL(string: "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY")!
//URLSessionDataTask handles the req and returns the data which you will decode based on the Item structure we defined above.
let task = URLSession.shared.dataTask(with: url) { (data, _, _) in
let jsonDecoder = JSONDecoder()
if let data = data,
let item = try? jsonDecoder.decode(Item.self, from: data){
//jsonDecoder requires a type of our structure represented by .self and the data from the request.
completion(item)
} else {
completion(nil)
}
}
task.resume()
}
func fetchItemPhoto(usingURL url: URL, completion: @escaping (Data?)-> Void) {
let task = URLSession.shared.dataTask(with: url) { (data, _, _) in
if let data = data { completion(data) } else { completion(nil) }
}
task.resume()
}
}
Теперь в вашем ViewController вызовите свой запрос и обработайте выполнение вашего закрытия.
class ViewController: UIViewController {
let requestCtrl = RequestCtrl()
override func viewDidLoad() {
super.viewDidLoad()
requestCtrl.fetchItem { (fetchedItem) in
guard let fetchedItem = fetchedItem else { return }
self.getPhoto(with: fetchedItem)
}
}
func getPhoto(with item: Item) {
requestCtrl.fetchItemPhoto(usingURL: item.url) { (fetchedPhoto) in
guard let fetchedPhoto = fetchedPhoto else { return }
let photo = UIImage(data: fetchedPhoto)
//now you have a photo at your disposal
}
}
}
Это не лучшие практики, так как я все еще учусь, поэтому обязательно проведите небольшое исследование по темам, особенно по закрытию, параллелизму ios и URLComponents в документации Apple :)
Вы можете использовать это расширение
extension UIImage {
public static func loadFrom(url: URL, completion: @escaping (_ image: UIImage?) -> ()) {
DispatchQueue.global().async {
if let data = try? Data(contentsOf: url) {
DispatchQueue.main.async {
completion(UIImage(data: data))
}
} else {
DispatchQueue.main.async {
completion(nil)
}
}
}
}
}
С использованием
guard let url = URL(string: "http://myImage.com/image.png") else { return }
UIImage.loadFrom(url: url) { image in
self.photo.image = image
}
В Swift следует использовать
URL, а неNSURL. ИспользуйтеData, а неNSData. Используйте словарь Swift, а неNSDictionary.