Swift Unit Test empty image - unit-testing

I'm trying to test if an image has been set by an imageView extension. I have the test passing when trying to load in from an http url. Now I want to test if the image gets set from an https url.
My current code is:
func test_DateImageLoadedFromHTTPSURL() {
let expected = expectation(description: "Image from https did load")
let viewer = UIImageView(frame: CGRect(x: 0, y: 0, width: 300, height: 250))
viewer.imageFromServerURL(urlString: "https://dummyimage.com/300x250/000/fff.png")
if viewer.image != nil {
expected.fulfill()
} else {
XCTFail()
}
waitForExpectations(timeout: 3.0, handler: nil)
}
It should work, unless I'm not seeing it.
Thanks

The order that you are doing things here will never work. Your method is asynchronous here so the order of things happening will be something like...
Run the method to get the image
Check the imageView.image is not nil (it is still nil at this point)
Wait for expectations
Download of image finished
Set the image
The method is asynchronous so the order of things happening changes.
You need to add some sort of completion to your method so that you can check the image is not nil AFTER it has been set and the completion been called.
Like...
viewer.imageFromServerURL(urlString: "https://dummyimage.com/300x250/000/fff.png") {
// image has finished loading here...
// check the image is not nil
}
Of course, you will need to update the actual method so that it will accept a closure and run the closure once the image is loaded.

Related

SwiftUI: Custom view is un-hittable in XCTest

During Xcode UI test, I found my custom view's (MenuViewButton) is un-hittable in a test, I could found it but cannot touch it. In debug , when I po isHittable in console, it returns false. However I'm not sure if this is the correct behavior.
Per this thread XCUIElement exists, but is not hittable said, isHittable is default false for custom view element, and default true for UIKit standard view. But I don't know if it is the same behavior in SwiftUI.
Since the way someView.isAccessibilityElement = true is not possible in SwiftUI. My question is how could I let my custom view became hittable? Then it could be tapped in a test.
private var aView: some View {
MenuViewButton(
image: Image("an image name"),
text: Text("a string")
)
.accessibility(identifier: "xxx name")
}
I also use force tap with coordinate in case tap() is not working, but to give a offset of normalizedOffset didn't fix the problem in all places, it means some un-hittable element could be tapped, that is great but some others still not.
So may I know where normalizedOffset is start, from the middle of frame to give the offset or the top left?
func forceTapElement(timeout: TimeInterval) {
if !self.waitForExistence(timeout: timeout) {
return
}
if self.isHittable {
self.tap()
} else {
let coordinate: XCUICoordinate = self.coordinate(withNormalizedOffset: CGVector(dx: 0.1, dy: 0.0))
coordinate.tap()
}
}
Add https://github.com/devexperts/screenobject as a dependency
Use .tapUnhittable() instead of .tap() for this particular view. It gets the coordinates and taps using them
I have the same situation and am looking for answers. What has worked for me was to use:
let coordinate = element.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
Still this seems like workaround for a real problem. One other strange thing is that i do not have this problem on iOS15.0 simulator, only for later versions. Currently trying with iOS15.2
One more thing I've tried is to add
.accessibilityElement(children: .combine)
and specificly telling it's a button with
.accessibility(addTraits: .isButton)
But this doesn't solve the problem.
Seems that isAccessibilityElement would be an answer here, but SwiftUI doesn't seem to have such.

Show downloaded mp4 inside a view like VStack and play it AVPlayer, SwiftUI

I'm working on the offline downloads section for a video streaming app. Currently, I'm able to download videos and also can see the file path for the downloads as follows:
Button(action: {
let fileManager = FileManager.default
do {
let resourceKeys : [URLResourceKey] = [.creationDateKey, .isDirectoryKey]
let documentsURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let enumerator = FileManager.default.enumerator(at: documentsURL,
includingPropertiesForKeys: resourceKeys,
options: [.skipsHiddenFiles], errorHandler: { (url, error) -> Bool in
print("directoryEnumerator error at \(url): ", error)
return true
})!
for case let fileURL as URL in enumerator {
let resourceValues = try fileURL.resourceValues(forKeys: Set(resourceKeys))
// print(fileURL.path, resourceValues.creationDate!, resourceValues.isDirectory!)
print(fileURL.path)
self.array.append(fileURL.path)
}
} catch {
print(error)
}
}){
Text("Check downloads")
}
//response when button is clicked
/private/var/mobile/Containers/Data/Application/AD90B554-3465-43DD-BAE2-04BC83F850A3/Documents/Bz75srG5nctDCnAWIspM.mp4
/private/var/mobile/Containers/Data/Application/AD90B554-3465-43DD-BAE2-04BC83F850A3/Documents/GWYgrgDVYaowxeGcOjzp.mp4
Since I've renamed the mp4 files using movie ID's (eg: GWYgrgDVYaowxeGcOjzp.mp4) my goal is to use this name to show which videos were downloaded and allow users to play the video. I'm able to do the name processing later on, but how am I able to get the downloaded mp4 files into a view like a VStack? I was thinking of looping through and adding the files into an array but wanted to check if there's a different way. Also, would be great if you could show me how to play a video using the path as the url. I've tried applying suggested methods but ended up with a "NSURLConnection finished with error - code -1002" error. Thank you!

List addAll throw error :Unhandled Exception: Concurrent modification during iteration:

What is actually happening?
main.dart
In initState, we are calling server to get image from database, then add it into sink
var capturedImagesList = List<dynamic>();
#override
void initState() {
super.initState();
_bloc.getImages(); //
});
}
bloc class
final _urlImage = BehaviorSubject<List<dynamic>>();
get urlImageSink => _urlImage.sink;
get urlImageStream => _urlImage.stream;
Future getImages() async {
Response image = await _repo.getImages(); // call server
var imageResponse = Response.fromJson(image.body);
urlImageSink.add(imageResponse.images); // add image to sink
}
The retrieved image will be displayed in horizontal ListView.
When the + image is clicked, it will allow user to select an image from gallery, then placed beside the ListView as image below.
Everything works fine !
But when I click the + image again to add one more image, it throw us this error
[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception:
Concurrent modification during iteration: Instance(length:3) of
'_GrowableList'. E/flutter (32313): #0 List.addAll
(dart:core-patch/growable_array.dart:187:11)
main.dart
This is the code after image is selected from gallery.
#override
List<dynamic> userImage(File images) {
if (images != null) {
capturedImagesList.add(images.path); // add selected image to List
capturedImagesList.addAll(_bloc.urlImageStream.value); // add the server image to list
_bloc.urlImageSink.add(capturedImagesList); // add all images to list
}
return null;
}
Errror is pointed to this line
capturedImagesList.addAll(_bloc.urlImageStream.value);
Why it works for the first time, but failed for second time?
You could just do that
_bloc.urlImageSink.add([images])
And the image would reflected immediately in UI.
I think the problem is in this line
capturedImagesList.addAll(_bloc.urlImageStream.value);
this looks strange, you can't copy the stream into array, it won't work.
Did you check out this thread: Exception: Concurrent modification during iteration: Instance(length:17) of '_GrowableList'
I quote from the article:
This error means that you are adding or removing objects from a collection during iteration. This is not allowed since adding or removing items will change the collection size and mess up subsequent iteration.
So I don't see all the code, but could it be that when you add a picture via the add button, you clear the list or remove the current items?
Try to clear the array like this before doing addAll
_myLeaveRequests?.clear();

How to work with DispatchQueue.global?

I am downloading images from my server using this
let queue = DispatchQueue.global(qos: .userInteractive)
queue.async{ ...
And while images downloading user can tap the button. But sometimes this button doesn't work (when all images done button work correct).
How to solve this problem?
Donot access global queue for that, instead create a custom DispatchQueue with label, qos as .background and fetch the image, when done use DispatchQueue.main to return the image to the main queue
For e.g:
let queue = DispatchQueue(label: "myImageQueue", qos: .background)
queue.async {
...fetch image
DispatchQueue.main.async {
self.imageView.image = fetchedImage
}
}

Dynamic collectionView with UIimageView height

I want to show images like https://www.pinterest.com in collectionView using swift 3. For that the image is coming from backend with just URL not the image sizes. So i want to dynamically show cell according to height of images.
What i am doing is,
let url = self.myArray[indexPath.row].image
let data = NSData(contentsOf:URL(string: url)!)
var photo = UIImage()
if (data?.length)! > 0 {
photo = UIImage(data:data! as Data)!
}
let boundingRect = CGRect(x: 0, y: 0, width: width, height: CGFloat(MAXFLOAT))
let rect = AVMakeRect(aspectRatio: photo.size, insideRect: boundingRect)
return rect.size.height
It return me the height.
Problem is: Images are HD with upto 3MB size.
let data = NSData(contentsOf:URL(string: url)!)
takes so much time, as i have 20 to 30 images.
Is there any way to download the image on another thread instead of main thread so downloading image will continue in background,
OR anyone have a better solution for calculating height of images from URL.
PS: I have followed https://www.raywenderlich.com/107439/uicollectionview-custom-layout-tutorial-pinterest for customLayout.
Thanks in advance.
You should look into Alamofire. It uses promises to make the requests asynchronously without holding up the UI thread. Your code would look something like this
Alamofire.request(URL_STRING, method: .get, parameters: nil, encoding: URLEncoding.default, headers: getHeaders())
.validate()
.responseImage(completionHandler: {response in
switch response.result {
case .success:
//The image will be stored in response.data in NSData format
break
case .failure(let error):
//Handle errors over here
break
}
})