I'm trying to write a URL Validator in Swift w/ Combine and having a SwiftUI view subscribe to it. It seems to work fine in the simulator but crashes on my dev phone (running 13.1).
Scenario... User types into a UITextField which is connected to the urlString var in the view model. When that changes I clean the string, create a URL, and then do a HEAD test with URLSession. This all works in the sim, but on tap of the text field on the device it crashes the app and I'm not getting any good stack traces. Any ideas?
static func testURLPublisher(string: String) -> AnyPublisher<URL?, Never> {
let validatedURL = try? validateURL(string: string)
guard let urlToCheck = validatedURL else {
return Just(nil).eraseToAnyPublisher()
}
var request = URLRequest(url: urlToCheck)
request.httpMethod = "HEAD"
let publisher = URLSession.shared.dataTaskPublisher(for: request)
.handleEvents(receiveSubscription: { _ in
networkActivityPublisher.send(true)
}, receiveCompletion: { _ in
networkActivityPublisher.send(false)
}, receiveCancel: {
networkActivityPublisher.send(false)
})
.tryMap { data, response -> URL? in
// URL Responded - Check Status Code
guard let urlResponse = response as? HTTPURLResponse, ((urlResponse.statusCode >= 200 && urlResponse.statusCode < 400) || urlResponse.statusCode == 405) else {
throw URLValidatorError.serverError("Could not find the a servr at: \(urlToCheck)")
}
return urlResponse.url?.absoluteURL
}
.catch { err in
return Just(nil)
}
.eraseToAnyPublisher()
return publisher
}
The view that is using it looks like this...
class NewSiteViewModel: ObservableObject {
@Published var validatedURL: URL?
//@Published var secretKey: String?
@Published var urlString: String = ""
@Published var isValidURL: Bool = false
private var cancellable = Set<AnyCancellable>()
init() {
$urlString
.dropFirst(1)
.throttle(for: 0.5, scheduler: DispatchQueue(label: "Validator"), latest: true)
.removeDuplicates()
.compactMap { string -> AnyPublisher<URL?, Never> in
return URL.testURLPublisher(string: string)
}
.switchToLatest()
.receive(on: RunLoop.main)
.sink { recievedURL in
guard let url = recievedURL else {
self.validatedURL = nil
self.isValidURL = false
return
}
self.validatedURL = url
self.isValidURL = true
}
.store(in: &cancellable)
}
}