I've tried to return String from my function, but I get error "Use of unresolved identifier nameOfFlower". Here's my function:
func detectFlower(image: CIImage) -> String {
guard let model = try? VNCoreMLModel(for: FlowerModels().model) else {
fatalError("Cannot import a model.")
}
let request = VNCoreMLRequest(model: model) { (request, error) in
let classification = request.results?.first as? VNClassificationObservation
var nameOfFlower = String(classification?.identifier ?? "Unexpected type")
}
let handler = VNImageRequestHandler(ciImage: image)
do {
try handler.perform([request])
} catch {
print(error)
}
return nameOfFlower
}
What is wrong with code?
Its async code .. so use closure as completion block
func detectFlower(image: CIImage,completion: #escaping (_ getString:String?,_ error:Error?)-> Void) {
guard let model = try? VNCoreMLModel(for: FlowerModels().model) else {
fatalError("Cannot import a model.")
}
let request = VNCoreMLRequest(model: model) { (request, error) in
let classification = request.results?.first as? VNClassificationObservation
var nameOfFlower = String(classification?.identifier ?? "Unexpected type")
completion(nameOfFlower,nil)
}
let handler = VNImageRequestHandler(ciImage: image)
do {
try handler.perform([request])
} catch {
print(error)
completion(nil,error)
}
}
How to use
detectFlower(image: yourImage) { (flowerString, error) in
// you get optional flower string here
}
Related
Im new to swift and I was trying to make an app that can parse the from on a screenshot. I have the following code so far, and I wasnt able to figure out proper way to call the recognize function in my ContentView, any help is appreciated:
`
struct ContentView: View {
#State var selectedItems: PhotosPickerItem?
#State var selectedPhotoData: Data?
func recogText(selData: Data?)
{
if let selData = selData, let image = UIImage(data: selData){
guard let cgImage = image.cgImage else {return}
let handler = VNImageRequestHandler(cgImage: cgImage)
let request = VNDetectTextRectanglesRequest { request, error in
guard let observations = request.results as? [VNRecognizedTextObservation],
error == nil else {return}
let text = observations.compactMap({
$0.topCandidates(1).first?.string
}).joined(separator: ", ")
print(text.count)
}
do {
try handler.perform([request])
}
catch {
print("Unable to perform the requests: \(error).")
}
}
}
var body: some View {
VStack{
//Icon
mainImage()
//Button
PhotosPicker(selection: $selectedItems, matching: .images) {
Label("Select a photo", systemImage: "photo")
}
.tint(.blue)
.controlSize(.large)
.buttonStyle(.borderedProminent)
.onChange(of: selectedItems) { newItem in
Task {
if let data = try? await newItem?.loadTransferable(type: Data.self) {
selectedPhotoData = data
let _ = recogText(selData: data)
}
}
}
}
}
}
`
Expected a print of the parsed text but no output was found
Hello here is an example function that might help you. Of course you have to replace the TestImage with yours. This might work for you and you need to import Vision
func recogText() {
let textRecognitionRequest = VNRecognizeTextRequest { (request, error) in
// Insert code to process the text recognition results here
guard let observations = request.results as? [VNRecognizedTextObservation] else { return }
for observation in observations {
let topCandidate = observation.topCandidates(1).first
if let recognizedText = topCandidate?.string {
print(recognizedText)
}
}
}
textRecognitionRequest.recognitionLevel = .accurate
let image = UIImage(named: "TestImage")
let imageRequestHandler = VNImageRequestHandler(cgImage: (image?.cgImage!)!, options: [:])
do {
try imageRequestHandler.perform([textRecognitionRequest])
} catch {
print(error)
}
}
func downloadImage(urlStr : String , completionHandler : #escaping (DownloadImage) -> Void) {
completionHandler(.serviceStarts)
DispatchQueue.global().async {
guard let url = URL(string: urlStr) else {
completionHandler(.notcompleted)
return
}
let dataTask = URLSession.shared.dataTask(with: url) { data, response, error in
guard let dt = data else{
completionHandler(.notcompleted)
return
}
guard let img = UIImage(data: dt) else {
completionHandler(.notcompleted)
return
}
completionHandler(.successful(img))
}
dataTask.resume()
}
}
enum DownloadImage {
case serviceStarts
case notcompleted
case successful(UIImage)
}
Hi i am getting following errors while iterating over a custom object with for try await:-
For-in loop requires '[Photo]' to conform to 'AsyncSequence'
Type of expression is ambiguous without more context
Custom object:-
enum FetchError: Error {
case badImage
case badRequest
case invalidImageURL
case noURL
case failedToFetchImage
}
struct Photo: Codable {
let albumId: Int
let id: Int
let title: String
let urlPath: String
let thumbnailUrl: String
}
Working code for fetching 1st image:-
Class ViewController: UIViewController {
func fetchAsyncImage(request:URLRequest) async throws -> UIImage {
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badRequest }
let photos = try JSONDecoder().decode([Photo].self, from: data)
guard let imagePath = photos.first?.urlPath,
let imageURL = URL.init(string: imagePath) else { throw FetchError.noURL }
let (imageData, imageResponse) = try await URLSession.shared.data(from: imageURL)
guard (imageResponse as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.invalidImageURL }
guard let firstImage = UIImage(data: imageData) else { throw FetchError.badImage }
return firstImage
}
Issue while performing async sequence on Photo object
func fetchAsyncImage(request:URLRequest) async throws -> [UIImage] {
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badRequest }
let photos = try JSONDecoder().decode([Photo].self, from: data)
guard let imagePath = photos.first?.urlPath,
let imageURL = URL.init(string: imagePath) else { throw FetchError.noURL }
var imageArr:[UIImage] = []
for await photo in photos {
guard let imagePath = photo.urlPath,
let imageURL = URL.init(string: imagePath) else { throw FetchError.noURL }
do {
let (imageData, imageResponse) = try await URLSession.shared.data(from: imageURL)
guard (imageResponse as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.invalidImageURL }
guard let image = UIImage(data: imageData) else { throw FetchError.badImage }
imageArr.append(image)
} catch {
throw FetchError.failedToFetchImage
}
}
return imageArr
}
Getting this error: -
What i tried for implementing async sequence:-
struct Photo: Codable, AsyncSequence {
typealias Element = URL
let albumId: Int
let id: Int
let title: String
let urlPath: String
let thumbnailUrl: String
struct AsyncIterator: AsyncIteratorProtocol {
let urlPath: String
mutating func next() async throws -> URL? {
do {
guard let imageURL = URL.init(string: urlPath) else { throw FetchError.noURL }
return imageURL
} catch {
throw FetchError.invalidImageURL
}
}
}
func makeAsyncIterator() -> AsyncIterator {
AsyncIterator(urlPath: urlPath)
}
}
i am not sure how to iterate over photo objects with "for try await"
photos is not an async sequence. An async sequence is a sequence that returns its next elements asynchronously. However, photos is an array of Photos, an array, everything is stored in memory. To fetch the next element, you just access the next thing in memory. There's nothing async about it. In this case, it is the processing (fetching the UIImage) of the array elements that involves an asynchronous operation, not the “fetching the next element” part, so you should use await in the loop body, which you correctly did.
A regular for loop will do the job:
for photo in photos {
guard let imagePath = photo.urlPath,
let imageURL = URL.init(string: imagePath) else { throw FetchError.noURL }
do {
let (imageData, imageResponse) = try await URLSession.shared.data(from: imageURL)
guard (imageResponse as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.invalidImageURL }
guard let image = UIImage(data: imageData) else { throw FetchError.badImage }
imageArr.append(image)
} catch {
throw FetchError.failedToFetchImage
}
}
The loop will fetch the first image, wait for it to finish, then fetch the second, wait for that to finish, and so on. IMO, you could just fetch them all at the same time with a task group, unless you have some special requirements that I'm not aware of.
Anyway, it is also incorrect to conform Photo to AsyncSequence. After all, one Photo isn't a sequence. What you can do instead, if you really want to use AsyncSequence, is to create a AsyncPhotoSequence struct that takes a [Photo]. Note that this is a sequence of UIImage.
struct AsyncPhotoSequence: AsyncSequence {
let photos: [Photo]
typealias Element = UIImage
struct AsyncPhotoIterator: AsyncIteratorProtocol {
var arrayIterator: IndexingIterator<[Photo]>
mutating func next() async throws -> UIImage? {
guard let photo = arrayIterator.next() else { return nil }
// Here you are supposed to check for cancellation,
// read more here:
// https://developer.apple.com/documentation/swift/asynciteratorprotocol#3840267
// copied from your loop body
guard let imagePath = photo.urlPath,
let imageURL = URL.init(string: imagePath) else { throw FetchError.noURL }
do {
let (imageData, imageResponse) = try await URLSession.shared.data(from: imageURL)
guard (imageResponse as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.invalidImageURL }
guard let image = UIImage(data: imageData) else { throw FetchError.badImage }
return image
} catch {
throw FetchError.failedToFetchImage
}
}
}
func makeAsyncIterator() -> AsyncPhotoIterator {
AsyncPhotoIterator(arrayIterator: photos.makeIterator())
}
}
Then you can use for try await with AsyncPhotoSequence:
for try await image in AsyncPhotoSequence(photos: photos) {
imageArr.append(image)
}
But I personally wouldn't go to that trouble.
I am using nsurlsession on RxSwift.
I am facing two problems about nsurlsession on RxSwift.
I created Custom Observable.
This Observable has used nsurlsession.
nsurlsession.datataskwithrequst was canceled everytime on RxSwift.
My code is here
func getWorkInfo(request:NSURLRequest,type1:C.Type,type2:W.Type? = nil) -> Observable<(C?,[W]?, NSHTTPURLResponse)>{
return Observable.create { observer in
var d: NSDate?
if Logging.URLRequests(request) {
d = NSDate()
}
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) in
guard let response = response, data = data else {
ColorLogger.defaultInstance?.error(error)
observer.on(.Error(error ?? RxCocoaURLError.Unknown))
return
}
guard let httpResponse = response as? NSHTTPURLResponse else {
observer.on(.Error(RxCocoaURLError.NonHTTPResponse(response: response)))
return
}
guard let jsonString: String = NSString(data:data, encoding:NSUTF8StringEncoding) as? String else{
observer.on(.Error(ApiError.FormatError))
return
}
//カウント系
let countObj = Mapper<C>().map(jsonString)
//一覧系
//二つ目を指定してない場合はnilを返す
if type2 != nil{
let aryObj = Mapper<W>().mapArray(jsonString)
observer.on(.Next(countObj,aryObj, httpResponse))
}else{
observer.on(.Next(countObj,nil, httpResponse))
}
observer.on(.Completed)
}
let t = task
t.resume()
return AnonymousDisposable{task.cancel()}
}
}
Above method was called by here.
func getWorkCount(dicParam: NSDictionary) -> Observable<WorkCount?> {
// URL作成
let strParam = dicParam.urlEncodedString()
let strUrl = Const.ShiftApiBase.SFT_API_DOMAIN+Const.ApiUrl.WORK_COUNT+"?"+strParam
// 求人リストデータを取得
let url = NSURL(string: strUrl)!
let request = NSURLRequest(URL: url)
let client = WorkClient<WorkCount,Work>()
ColorLogger.defaultInstance?.debug(strUrl)
return client.getWorkInfo(request, type1: WorkCount.self)
.observeOn(Dependencies.sharedDependencies.backgroundWorkScheduler)
.catchError{(error) -> Observable<(WorkCount?,[Work]?, NSHTTPURLResponse)> in
print("error")
ColorLogger.defaultInstance?.error("UnknownError")
return Observable.empty()
}
.map { countObj,workObj,httpResponse in
if httpResponse.statusCode != 200 {
throw ApiError.Bad
}
return countObj
}
.observeOn(Dependencies.sharedDependencies.mainScheduler)
}
And My subscribe is here.
/**
検索件数を取得
- parameter param: <#param description#>
*/
func getSearchCount(param: [String:String]){
let dicParam = NSDictionary(dictionary: param)
api.getWorkCount(dicParam)
.catchError{
error -> Observable<WorkCount?> in
switch error{
case ApiError.Bad:
ColorLogger.defaultInstance?.error("status error")
break
case ApiError.FormatError:
ColorLogger.defaultInstance?.error("FormatError")
break
case ApiError.NoResponse:
ColorLogger.defaultInstance?.error("NoResponse")
break
default:
ColorLogger.defaultInstance?.error("UnKnownError")
break
}
return Observable.just(nil)
}
.subscribeNext { [weak self] countObj in
self?.count.value = countObj?.returned_count
}
.addDisposableTo(disposeBag)
}
I have two problems.
1:nsurlsession was canceled every time.I don know reason.
2:Even if I got error on NSURLSession,I could not catch error on "CatchError".
By the way,when i try to use the following code,But nsurlsession might be canceled.
It might be a base nsurlsession on RxSwift.
func getWorkCount4(dicParam: NSDictionary) -> Observable<WorkCount?> {
// URL作成
let strParam = dicParam.urlEncodedString()
let strUrl = Const.ShiftApiBase.SFT_API_DOMAIN+Const.ApiUrl.WORK_COUNT+"?"+strParam
// 求人リストデータを取得
let url = NSURL(string: strUrl)!
let request = NSURLRequest(URL: url)
let session = ApiBase.sharedObj.createNSURLSession()
return NSURLSession.sharedSession().rx_response(request)
.observeOn(Dependencies.sharedDependencies.backgroundWorkScheduler)
.map { data,httpResponse in
if httpResponse.statusCode != Const.HTTP_RESPONSE.HTTP_STATUS_CODE_OK {
throw ApiError.Bad
}
guard let jsonString: String = NSString(data:data, encoding:NSUTF8StringEncoding) as? String else{
throw ApiError.FormatError
}
let countObj = Mapper<WorkCount>().map(jsonString)
return countObj
}
.observeOn(Dependencies.sharedDependencies.mainScheduler)
}
What is this problem?
I could resolve by myself.
Above method does not have any problem.
Below code has problem.
// MARK: - 検索カウント
extension SearchCount{
/// 検索用のViewModel
var searchViewModel:SearchViewModel {
return SearchViewModel()
}
/**
検索の件数を取得
*/
func getSearchCount(){
setApiParameter()
searchViewModel.getSearchCount(apiParam)
}
}
I defined searchViewModel on Class,the i could resolve.
I've written a function that should return a value but the value comes from a closure. The problem is if I try to return a value from inside the closure it treats this as being the return value from the completion handler.
private func loadData() throws -> [Item] {
var items = [Item]()
let jsonUrl = "http://api.openweathermap.org/data/2.5/forecast/daily?units=metric&cnt=7&q=coventry,uk"
print(jsonUrl)
let session = NSURLSession.sharedSession()
guard let shotsUrl = NSURL(string: jsonUrl) else {
throw JSONError.InvalidURL(jsonUrl)
}
session.dataTaskWithURL(shotsUrl, completionHandler: {(data, response, error) -> Void in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: [])
print(json)
guard let days:[AnyObject] = (json["list"] as! [AnyObject]) else {
throw JSONError.InvalidArray
}
for day in days {
guard let timestamp:Double = day["dt"] as? Double else {
throw JSONError.InvalidKey("dt")
}
print(timestamp)
let date = NSDate(timeIntervalSince1970: NSTimeInterval(timestamp))
guard let weather:[AnyObject] = day["weather"] as? [AnyObject] else {
throw JSONError.InvalidArray
}
guard let desc:String = weather[0]["description"] as? String else {
throw JSONError.InvalidKey("description")
}
guard let icon:String = weather[0]["icon"] as? String else {
throw JSONError.InvalidKey("icon")
}
guard let url = NSURL(string: "http://openweathermap.org/img/w/\(icon).png") else {
throw JSONError.InvalidURL("http://openweathermap.org/img/w/\(icon).png")
}
guard let data = NSData(contentsOfURL: url) else {
throw JSONError.InvalidData
}
guard let image = UIImage(data: data) else {
throw JSONError.InvalidImage
}
guard let temp:AnyObject = day["temp"] else {
throw JSONError.InvalidKey("temp")
}
guard let max:Float = temp["max"] as? Float else {
throw JSONError.InvalidKey("max")
}
let newDay = Item(date: date, description: desc, maxTemp: max, icon: image)
print(newDay)
items.append(newDay)
}
return items // this line fails because I'm in the closure. I want this to be the value returned by the loadData() function.
} catch {
print("Fetch failed: \((error as NSError).localizedDescription)")
}
})
}
Add a completion handler (named dataHandler in my example) to your loadData function:
private func loadData(dataHandler: ([Item])->()) throws {
var items = [Item]()
let jsonUrl = "http://api.openweathermap.org/data/2.5/forecast/daily?units=metric&cnt=7&q=coventry,uk"
print(jsonUrl)
let session = NSURLSession.sharedSession()
guard let shotsUrl = NSURL(string: jsonUrl) else {
throw JSONError.InvalidURL(jsonUrl)
}
session.dataTaskWithURL(shotsUrl, completionHandler: {(data, response, error) -> Void in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: [])
print(json)
guard let days:[AnyObject] = (json["list"] as! [AnyObject]) else {
throw JSONError.InvalidArray
}
for day in days {
guard let timestamp:Double = day["dt"] as? Double else {
throw JSONError.InvalidKey("dt")
}
print(timestamp)
let date = NSDate(timeIntervalSince1970: NSTimeInterval(timestamp))
guard let weather:[AnyObject] = day["weather"] as? [AnyObject] else {
throw JSONError.InvalidArray
}
guard let desc:String = weather[0]["description"] as? String else {
throw JSONError.InvalidKey("description")
}
guard let icon:String = weather[0]["icon"] as? String else {
throw JSONError.InvalidKey("icon")
}
guard let url = NSURL(string: "http://openweathermap.org/img/w/\(icon).png") else {
throw JSONError.InvalidURL("http://openweathermap.org/img/w/\(icon).png")
}
guard let data = NSData(contentsOfURL: url) else {
throw JSONError.InvalidData
}
guard let image = UIImage(data: data) else {
throw JSONError.InvalidImage
}
guard let temp:AnyObject = day["temp"] else {
throw JSONError.InvalidKey("temp")
}
guard let max:Float = temp["max"] as? Float else {
throw JSONError.InvalidKey("max")
}
let newDay = Item(date: date, description: desc, maxTemp: max, icon: image)
print(newDay)
items.append(newDay)
}
dataHandler(items)
} catch {
print("Fetch failed: \((error as NSError).localizedDescription)")
}
}).resume()
}
do {
try loadData { itemsArray in
print(itemsArray)
}
} catch {
print(error)
}
I've tested it in a Playground and it works without errors: