I'm new to Swift and SwiftUI.
In my macOS SwiftUI project, I'm trying to verify that a URL is reachable so I can present one of two views conditionally. One view which loads the image URL, another one which displays an error image if the URL is not reachable.
Here's my URL extension with completion:
import Foundation
extension URL {
func isReachable(completion: @escaping (Bool) -> Void) {
var request = URLRequest(url: self)
request.httpMethod = "HEAD"
request.timeoutInterval = 1.0
URLSession.shared.dataTask(with: request) { data, response, error in
if error != nil {
DispatchQueue.main.async {
completion(false)
}
return
}
if let httpResp: HTTPURLResponse = response as? HTTPURLResponse {
DispatchQueue.main.async {
completion(httpResp.statusCode == 200)
}
return
} else {
DispatchQueue.main.async {
completion(false)
}
return
}
}.resume()
}
}
Elsewhere, I'm trying to use that in a model-view:
var imageURL: URL? {
if let url = self.book.image_url {
return URL(string: url)
} else {
return nil
}
}
var imageURLIsReachable: Bool {
if let url = self.imageURL {
url.isReachable { result in
return result // Error: Cannot convert value of type 'Bool' to closure result type 'Void'
}
} else {
return false
}
}
Though Xcode is showing this error:
Cannot convert value of type 'Bool' to closure result type 'Void'
What am I doing wrong?
I got this to work after reading some of the comments here and doing more research/experimentation. Here's what I changed:
In the URL extension, I left it pretty much the same as I find it more readable this way. I did push the timeoutInterval
to a parameter:
// Extensions/URL.swift
import Foundation
extension URL {
func isReachable(timeoutInterval: Double, completion: @escaping (Bool) -> Void) {
var request = URLRequest(url: self)
request.httpMethod = "HEAD"
request.timeoutInterval = timeoutInterval
URLSession.shared.dataTask(with: request) { data, response, error in
if error != nil {
DispatchQueue.main.async {
completion(false)
}
return
}
if let httpResp: HTTPURLResponse = response as? HTTPURLResponse {
DispatchQueue.main.async {
completion(httpResp.statusCode == 200)
}
return
} else {
DispatchQueue.main.async {
completion(false)
}
return
}
}.resume()
}
}
I modified my BookViewModel
to make two of the properties to @Published
and used the URL extension there:
// View Models/BookViewModel.swift
import Foundation
class BookViewModel: ObservableObject {
@Published var book: Book
@Published var imageURLIsReachable: Bool
@Published var imageURL: URL?
init(book: Book) {
self.book = book
self.imageURL = nil
self.imageURLIsReachable = false
if let url = book.image_url {
self.imageURL = URL(string: url)
self.imageURL!.isReachable(timeoutInterval: 1.0) { result in
self.imageURLIsReachable = result
}
}
}
// Rest of properties...
}
Now my BookThumbnailView
can properly display the conditional views:
// Views/BookThumbnailView.swift
import SwiftUI
import Foundation
import KingfisherSwiftUI
struct BookThumbnailView: View {
@ObservedObject var viewModel: BookViewModel
private var book: Book {
viewModel.book
}
@ViewBuilder
var body: some View {
if let imageURL = self.viewModel.imageURL {
if self.viewModel.imageURLIsReachable {
KFImage(imageURL)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 70)
.cornerRadius(8)
} else {
ErrorBookThumbnailView()
}
} else {
DefaultBookThumbnailView()
}
}
}
Whew, that was quite the learning experience. Thanks to everyone who commented with suggestions and provided hints on where to look!
The problem literally laid in the line return result
, as Xcode tells you. When you create your function func isReachable(completion: @escaping (Bool) -> Void)
, you are telling Xcode that you are going to input something in the type of (Bool) -> Void
, which should be something like func someFunction(input: Bool) -> Void
.
But when you use a closure to input the completion handler, you're inputting a function in type Bool -> Bool
. Remove the line return result
, or change the type of the completion in your func isReachable(completion:)
.
Edit:
And indeed I don't recommend returning a async result in a computed property, that would cause some other problem.
I would change it to something like:
func isReachable(completion: @esacping (Bool) -> Void) {
...
}
func showResultView() {
guard let url = imageURL else {
// handling if the imageURL is nil
return
}
url.isReachable { result in
// do something with the result
if result {
// show viewController A
} else {
// show viewController B
}
}
}
// call showResultView anywhere you want, lets say you want to show it whenever the viewController appear
override func viewDidAppear() {
...
showResultView()
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.