I have just created a custom class for my purpose (export something to destination).
My extend class as below
import Foundation
import UIKit
class PBExtensionExport: NSObject {
static let instance = PBExtensionExport()
fileprivate var progress: Int = 0
func exportPhotos(images: [UIImage], completion: (Bool) -> ()) {
for image in images {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(updateStatus), nil)
// Progress monitoring
completion(images.count == progress)
}
}
#objc private func updateStatus() {
// Updating progress status
progress += 1
}
}
And this is how I called it in main proc:
PBExtensionExport.instance.exportPhotos(images: selectedImages, completion: { (success) -> Void in
if success {
// Do something
} else {
}
})
The problem is no compile error. But when I run my code, the command display runtime exception:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[NSInvocation setArgument:atIndex:]: index (2) out of bounds [-1, 1]'
Can anyone explain this problem for me please?
Your updateStatus takes no arguments while UIImageWriteToSavedPhotosAlbum's completionSelector parameter expects this method signature, according to the docs.
- (void)image:(UIImage *)image
didFinishSavingWithError:(NSError *)error
contextInfo:(void *)contextInfo;
In Swift, this would be something like
func updateStatus(image: UIImage, error: Error?, context: UnsafeRawPointer) {
// Your code here
}
You should go with Photos framework to save images. See below code with proper use of completion handler.
fileprivate var currentImageIndex = 0
typealias imagecompletionBlock = (_ isSuccess: Bool) -> Void
var arrImages:[UIImage]?
var imageSuccessBlock : imagecompletionBlock?
func saveImageToLibrary(images:[UIImage],completion:#escaping ((Bool) -> Void))
{
self.imageSuccessBlock = completion
self.currentImageIndex = 0
}
func saveImage()
{
if self.currentImageIndex == self.arrImages?.count
{
self.imageSuccessBlock!(true)
}else{
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAsset(from: self.arrImages![self.currentImageIndex])
}, completionHandler: { success, error in
DispatchQueue.main.async {
if success {
// Saved successfully!
self.saveImage()
}
else if let error = error {
// Save photo failed with error
}
else {
// Save photo failed with no error
}
}
})
}
}
Related
I'm integrating an iOS native SDK into React-Native. There's a function called SDK.getCardData which I want to use from RN. My first attempt was to call resolve and reject inside the closure:
import Foundation
import SDK
#objc(SwiftComponentManager)
class SwiftComponentManager: NSObject {
#objc
func getCardData(_ resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Void {
let cardId: String = "test"
let secret: String = "test"
SDK.getCardData(cardId, secret: secret) { (cardData, error) in
if (error != nil) {
reject(String(format: "Card data request failed: %#", error!.localizedDescription))
} else {
let pan = cardData!.pan
let cvv = cardData!.cvv
resolve(String(format: "Card data fetched successfully, pan: %#, cvv: %#", pan, cvv))
}
}
}
#objc func testMethod() -> Void {
print("This Does appear")
}
}
Unfortunately this throws the following error: escaping closure captures non-escaping parameter 'resolve'.
Second attempt:
import Foundation
import SDK
import Promises
#objc(SwiftComponentManager)
class SwiftComponentManager: NSObject {
#objc
func getCardData(_ resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Promise<CardData?> {
let cardId: String = "test"
let secret: String = "test"
let promise = Promise<CardData?>()
SDK.getCardData(cardId, secret: secret) { (cardData, error) in
if (error != nil) {
promise.reject(error)
} else {
let pan = cardData!.pan
let cvv = cardData!.cvv
promise.resolve(cardData)
}
}
return promise
}
#objc func testMethod() -> Void {
print("This Does appear")
}
}
How to call the resolve & reject properly? It is important for the function to return Void, more here.
Found the answer, Just had to add #escaping to the arguments:
#objc func fling(_ options: NSDictionary, resolver resolve: #escaping RCTPromiseResolveBlock, rejecter reject: #escaping RCTPromiseRejectBlock) -> Void {
...
The UITest in question launches the app, taps a cell which pushes the Screen to be tested and then fails with a fatalError() when i make a change that i expect will call a fatalError().
How can i catch the fatalError on the UITest and use it to report that the UITest has failed?
Here is the UITest:
class ConcreteFoodScreenUITests: XCTestCase
{
let app = XCUIApplication()
override func setUpWithError() throws {
continueAfterFailure = false
app.launch()
}
func testPathToConcreteFoodScreen() throws {
//Tap Concrete Cell in FoodDashboard to go to the ConcreteFoodScreen
XCTAssertTrue(app.otherElements["FoodDashboard"].exists)
app.scrollViews.otherElements.tables.staticTexts["100 g, 100cal, P: 90g, F: 80g, C: 70g"].tap()
//ConcreteFoodScreen
XCTAssertTrue(app.otherElements["ConcreteFoodScreen"].exists)
app.tables.cells.containing(.staticText, identifier:"Scale").children(matching: .textField).element.tap()
app.keys["5"].tap() //FIXME: Crashes with a Fatal Error
}
}
Here is the code that is being triggered that i want to know about:
class ScaleCellTextField: SWDecimalTextField {
//there is more code here but not relevant
func updateFoodEntry() {
fatalError()
// if let scale = Double(self.text!) {
// concreteFood.scale = scale
// }
}
}
You can see that i commented out some code here to get it working.
I'm afraid you can't. FatalError() ends your app's process, so I'm not sure you can catch this kind of event.
EDIT:
But...
You can use our dear good old Darwin Notifications... In order to add create a communication channel between your apps : the tested app and the tester app.
You'll need to add a file to both your targets:
typealias NotificationHandler = () -> Void
enum DarwinNotification : String {
case fatalError
}
class DarwinNotificationCenter {
let center: CFNotificationCenter
let prefix: String
var handlers = [String:NotificationHandler]()
init(prefix: String = "com.stackoverflow.answer.") {
center = CFNotificationCenterGetDarwinNotifyCenter()
self.prefix = prefix
}
var unsafeSelf: UnsafeMutableRawPointer {
return Unmanaged.passUnretained(self).toOpaque()
}
deinit {
CFNotificationCenterRemoveObserver(center, unsafeSelf, nil, nil)
}
func notificationName(for identifier: String) -> CFNotificationName {
let name = prefix + identifier
return CFNotificationName(name as CFString)
}
func identifierFrom(name: String) -> String {
if let index = name.range(of: prefix)?.upperBound {
return String(name[index...])
}
else {
return name
}
}
func handleNotification(name: String) {
let identifier = identifierFrom(name: name)
if let handler = handlers[identifier] {
handler()
}
}
func postNotification(for identifier: String) {
let name = notificationName(for: identifier)
CFNotificationCenterPostNotification(center, name, nil, nil, true)
}
func registerHandler(for identifier: String, handler: #escaping NotificationHandler) {
handlers[identifier] = handler
let name = notificationName(for: identifier)
CFNotificationCenterAddObserver(center,
unsafeSelf,
{ (_, observer, name, _, _) in
if let observer = observer, let name = name {
let mySelf = Unmanaged<DarwinNotificationCenter>.fromOpaque(observer).takeUnretainedValue()
mySelf.handleNotification(name: name.rawValue as String)
}
},
name.rawValue,
nil,
.deliverImmediately)
}
func unregisterHandler(for identifier: String) {
handlers[identifier] = nil
CFNotificationCenterRemoveObserver(center, unsafeSelf, notificationName(for: identifier), nil)
}
}
extension DarwinNotificationCenter {
func postNotification(for identifier: DarwinNotification) {
postNotification(for: identifier.rawValue)
}
func registerHandler(for identifier: DarwinNotification, handler: #escaping NotificationHandler) {
registerHandler(for: identifier.rawValue, handler: handler)
}
func unregisterHandler(for identifier: DarwinNotification) {
unregisterHandler(for: identifier.rawValue)
}
}
Then, simply, in your tested application:
#IBAction func onTap(_ sender: Any) {
// ... Do what you need to do, and instead of calling fatalError()
DarwinNotificationCenter().postNotification(for: .fatalError)
}
To catch it in your test, just do the following:
// This is the variable you'll update when notified
var fatalErrorOccurred = false
// We need a dispatch group to wait for the notification to be caught
let waitingFatalGroup = DispatchGroup()
waitingFatalGroup.enter()
let darwinCenter = DarwinNotificationCenter()
// Register the notification
darwinCenter.registerHandler(for: .fatalError) {
// Update the local variable
fatalErrorOccurred = true
// Let the dispatch group you're done
waitingFatalGroup.leave()
}
// Don't forget to unregister
defer {
darwinCenter.unregisterHandler(for: .fatalError)
}
// Perform you tests, here just a tap
app.buttons["BUTTON"].tap()
// Wait for the group the be left or to time out in 3 seconds
let _ = waitingFatalGroup.wait(timeout: .now() + 3)
// Check on the variable to know whether the notification has been received
XCTAssert(fatalErrorOccurred)
And that's it...
Disclaimer: You should not use testing code within your production code, so the use of DarwinNotification should not appear in production. Usually I use compilation directive to only have it in my code in debug or tests, not in release mode.
I am making QR scanner. My code is working when all of it written in one place inside ViewController but when I modularised it then I am not getting callback inside AVCaptureMetadataOutputObjectsDelegate.
import Foundation
import UIKit
import AVFoundation
class CameraSource : NSObject {
private var session : AVCaptureSession?
private var inputDevice : AVCaptureDeviceInput?
private var videoPreviewLayer : AVCaptureVideoPreviewLayer?
private var captureMetadataOutput : AVCaptureMetadataOutput?
func setCaptureMetadataOutput() {
self.captureMetadataOutput = nil
self.captureMetadataOutput = AVCaptureMetadataOutput()
}
func getCaptureMetadataOutput() -> AVCaptureMetadataOutput? {
return self.captureMetadataOutput
}
func setInputDevice(inputDevice : AVCaptureDeviceInput?) {
self.inputDevice = inputDevice
}
func getInputDevice() -> AVCaptureDeviceInput? {
return self.inputDevice
}
func setSession(session : AVCaptureSession?) {
self.session = session
}
func getSession() -> AVCaptureSession? {
return self.session
}
func setMetadataObjects(metaObjects : [AVMetadataObject.ObjectType], delegate : AVCaptureMetadataOutputObjectsDelegate) {
assert(self.captureMetadataOutput != nil)
self.captureMetadataOutput!.setMetadataObjectsDelegate(delegate, queue: DispatchQueue.main)
self.captureMetadataOutput!.metadataObjectTypes = metaObjects
}
func initViewoPreviewLayer(videoGravity : AVLayerVideoGravity, orientation : AVCaptureVideoOrientation) {
assert(session != nil)
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: session!)
videoPreviewLayer!.videoGravity = videoGravity
videoPreviewLayer!.connection!.videoOrientation = orientation
}
func addVideoLayerToImageView(imageView : UIImageView) {
assert(self.videoPreviewLayer != nil)
imageView.layer.addSublayer(self.videoPreviewLayer!)
self.videoPreviewLayer!.frame = imageView.bounds
}
func startSession() {
assert(session != nil)
self.session!.startRunning()
}
/*==========================================================================
STATIC FUNCTIONS
==========================================================================*/
static func getBackCamera() -> AVCaptureDevice {
return AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .back)!
}
static func getFrontCamera() -> AVCaptureDevice {
return AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .front)!
}
static func isCameraAvailable() -> Bool {
if #available(iOS 10.0, *) {
let count : Int = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera],
mediaType: AVMediaType.video,
position: .unspecified).devices.count
if count > 0 { return true }
}
else {
let count = AVCaptureDevice.devices(for: AVMediaType.video).count
if count > 0 { return true }
}
return false
}
/*==========================================================================
CAMERA BUILDER CLASS
==========================================================================*/
class Builder {
var cameraSource : CameraSource
init() {
cameraSource = CameraSource()
}
func createSession() -> Builder {
if (cameraSource.getSession() != nil) {
cameraSource.setSession(session: nil)
}
cameraSource.setSession(session: AVCaptureSession())
return self
}
func setSessionPreset(preset : AVCaptureSession.Preset) -> Builder {
assert(cameraSource.getSession() != nil)
cameraSource.getSession()!.sessionPreset = preset
return self
}
func attachInputDevice(camera : AVCaptureDevice) throws -> Builder {
try self.prepareInputDevice(camera: camera)
try self.addInputToSession()
assert(cameraSource.inputDevice != nil)
return self
}
func addOutputToSessionForMetaData() throws -> CameraSource {
cameraSource.setCaptureMetadataOutput()
assert(cameraSource.getSession() != nil && cameraSource.getCaptureMetadataOutput() != nil)
if !cameraSource.getSession()!.canAddOutput(cameraSource.getCaptureMetadataOutput()!) {
throw AppErrorCode.cameraError("Unable to attach output to camera session")
}
cameraSource.getSession()!.addOutput(cameraSource.getCaptureMetadataOutput()!)
return self.cameraSource
}
/*==========================================================================
BUILDER PRIVATE FUNCTIONS
==========================================================================*/
private func prepareInputDevice(camera : AVCaptureDevice) throws {
do {
let inputDevice = try AVCaptureDeviceInput(device: camera)
cameraSource.setInputDevice(inputDevice: inputDevice)
} catch let error as NSError {
print(error.localizedDescription)
throw AppErrorCode.cameraError("Unable to attach input to camera session")
}
}
private func addInputToSession() throws {
if(cameraSource.getSession() == nil) {
throw AppErrorCode.cameraError("Unable to create camera session")
}
assert(cameraSource.getInputDevice() != nil && cameraSource.getSession()!.canAddInput(cameraSource.getInputDevice()!))
cameraSource.getSession()!.addInput(cameraSource.getInputDevice()!)
}
}
}
My QR scanner Code looks like
import UIKit
import Foundation
import AVFoundation
protocol QRScannerDelegate {
func scannedData(_ scannedString : String)
}
class QRScanner : NSObject {
private var cameraSource : CameraSource?
var delegate : QRScannerDelegate?
func prepareCamera (delegate : QRScannerDelegate) throws -> QRScanner {
do {
self.delegate = delegate
self.cameraSource = try CameraSource
.Builder()
.createSession()
.setSessionPreset(preset: .photo)
.attachInputDevice(camera: CameraSource.getBackCamera())
.addOutputToSessionForMetaData()
self.cameraSource!.setMetadataObjects(metaObjects: [.qr], delegate: self as AVCaptureMetadataOutputObjectsDelegate)
} catch let err as NSError {
print(err.localizedDescription)
self.cameraSource = nil
throw AppErrorCode.cameraError("Unable to process camera with one or more issue")
}
return self
}
func initViewoPreviewLayer(videoGravity : AVLayerVideoGravity, orientation : AVCaptureVideoOrientation) -> QRScanner{
assert(cameraSource != nil)
self.cameraSource!.initViewoPreviewLayer(videoGravity: videoGravity, orientation: orientation)
return self
}
func addVideoLayerToImageView(imageView : UIImageView) -> QRScanner{
assert(cameraSource != nil)
self.cameraSource!.addVideoLayerToImageView(imageView: imageView)
return self
}
func startSession() {
assert(cameraSource != nil)
self.cameraSource!.startSession()
}
}
extension QRScanner : AVCaptureMetadataOutputObjectsDelegate {
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
print("Delegate called")
if metadataObjects.count == 0 {
self.delegate?.scannedData("No Data")
} else {
let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
if metadataObj.type == AVMetadataObject.ObjectType.qr {
if metadataObj.stringValue != nil {
print("Scanner Getting data: \(metadataObj.stringValue!)")
self.delegate?.scannedData(metadataObj.stringValue!)
}
}
}
}
}
I have implemented the QRScannerDelegate in my ViewController but I am not getting anything in there. Moreover I am not getting callback inside AVCaptureMetadataOutputObjectsDelegate even.
I tried passing the ViewController instance as AVCaptureMetadataOutputObjectsDelegate then I was getting callback with the scanned info.
So My question is why is this happening?
1) When I am passing normal class as AVCaptureMetadataOutputObjectsDelegate I am not getting callback. But.
2) Whe I am passing UIViewController instance as AVCaptureMetadataOutputObjectsDelegate then I am able to get callback.
UPDATE
This is how I am calling prepareCamera from my View Controller
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
do {
try QRScanner().prepareCamera(delegate: self)
.initViewoPreviewLayer(videoGravity: .resizeAspectFill, orientation: .portrait)
.addVideoLayerToImageView(imageView: self.qrScannerImageView)
.startSession()
} catch {
print("Some Camera Error")
}
self.createOverlay()
}
Its hard to say for sure without knowing how you called prepareCamera as this is what triggers setMetadataObjectsDelegate but to me it looks like you may not be keeping a strong reference to QRScanner in your ViewController (instantiating it as in instance variable) Which could explain why the callback is getting hit when your ViewController is your AVCaptureMetadataOutputObjectsDelegate as the ViewController is still in memory.
It's also worth noting that if the ViewController is your QRScannerDelegate you will want to define delegate as weak var delegate : QRScannerDelegate? to prevent a memory leak.
EDIT:
Change
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
do {
try QRScanner().prepareCamera(delegate: self)
.initViewoPreviewLayer(videoGravity: .resizeAspectFill, orientation: .portrait)
.addVideoLayerToImageView(imageView: self.qrScannerImageView)
.startSession()
} catch {
print("Some Camera Error")
}
self.createOverlay()
}
to
var qrScanner = QRScanner()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
do {
try self.qrScanner.prepareCamera(delegate: self)
.initViewoPreviewLayer(videoGravity: .resizeAspectFill, orientation: .portrait)
.addVideoLayerToImageView(imageView: self.qrScannerImageView)
.startSession()
} catch {
print("Some Camera Error")
}
self.createOverlay()
}
and change
protocol QRScannerDelegate {
func scannedData(_ scannedString : String)
}
to
protocol QRScannerDelegate: class {
func scannedData(_ scannedString : String)
}
To Allow a weak delegate
AVCaptureMetadataOutputObjectsDelegate is tough, but you can do some really cool stuff with it! So keep at it.
I pulled some QRScanner code I wrote a while ago and put it into a gist for you if you want to check it out. Its a bit more stripped down than what you have, but you may find it helpful.
https://gist.github.com/aChase55/733ea89af1bfa80c65971d3bc691f0b2
I am trying to learn VIPER. I followed this tutorial. I have these Interactor and Presenter:
class PPresenter: ViewToPresenterProtocol {
var view: PresenterToViewProtocol?
var router: PresenterToRouterProtocol? = PRouter()
var interactor: PresenterToInteractorProtocol? = PInteractor()
func initiateFetch() {
interactor?.fetchMatches()
}
func showMatchScreen(navigationC: UIViewController) {
router?.pushToMatchDetailScreen(navigationC: navigationC)
}
}
extension PPresenter: InteractorToPresenterProtocol {
func matchFetched(match: MatchDetails?, banner: Banner?) {
print(match!)
print(banner!)
}
func matchFetchError() {
//TODO
}
}
class PInteractor: PresenterToInteractorProtocol {
var presenter: InteractorToPresenterProtocol? = PPresenter()
var live: Live?
var upcoming: Upcoming?
var banners: Banner?
func fetchMatches() {
let parameters = ["api_token" : Constants.USER_INFO["api_token"].rawValue,"player_id" : Constants.USER_INFO["player_id"].rawValue]
ServiceHelper.sharedInstance.sendRequest(path: "get-predictor", params: parameters, showSpinner: true) { (response, error) in
if let error = error {
print("Unable to fetch match listing",error.localizedDescription)
return
} else {
guard let obj = try? JSONDecoder().decode(MatchDetails.self, from: response.rawData()) else { self.presenter?.matchFetchError(); return }
guard let bannerObj = try? JSONDecoder().decode(Banner.self,from: response.rawData()) else {self.presenter?.matchFetchError(); return }
self.presenter?.matchFetched(match: obj, banner: bannerObj)
}
}
}
}
Now, what is happening here, I get the router working, the view is coming, it is calling presenter, the presenter is calling the interactor, the interactor is successfully calling the API and getting the data and now it is time to return the data received from Interactor to Presenter and here it constantly throwing the following error:
Thread 1 EXC_BAD_ACCESS (code=2, address=0x7ffeeb1aeff8)
I think you have a cyclic call, maybe your interactor is not fully initialized and then you want data from it and then you got "Bad access error".
I have a findObjectsInBackgroundWithBlock method in my viewController. Now i want to execute code, but just until this background method is finished. How can i do that?
I'm using the swift programming lanuage.
Here is some example code that could help you. It is not clear how you would restrict the code (PRE-BACKGROUND CODE) to run only while the background processing has completed. You may want to insert some code in the notification response function either to confirm that that PRE-BACKGROUND CODE is completed or to terminate it.
// ParseTestViewController.swift
import UIKit
import Foundation
import Parse
class ParseTestViewController: UIViewController {
var userData = [String]()
func codeToExecuteBeforeStringsAreAppended() {
}
func codeToExecuteAfterStringsAreAppended() {
// can use the array 'userData'
}
override func viewDidLoad() {
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "notificationResponse:",
name: "recordsLoaded",
object: nil
)
self.getUserdataForUsername("MyName")
/* ==========================
Insert code tto be executed immediately after making the call that must not use the data returned by Parse. The function returns right away and the background may not have completed.
*/
codeToExecuteBeforeStringsAreAppended()
}
func getUserdataForUsername (queryUserName: String) {
var query = PFQuery(className:"UserData")
query.whereKey("username", equalTo: queryUserName)
let notification = NSNotification(name: "userDataRetrieved", object: self)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
for object in objects! {
if let username = object["username"] as? String {
self.userData.append (username)
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
NSNotificationCenter.defaultCenter().postNotification(notification)
}
}
func notificationResponse (notification: NSNotification) {
// this is executed after the background processing is done
// Insert the code that uses the data retrieved from Parse
codeToExecuteAfterStringsAreAppended()
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
This is pretty well covered in the documentation and guide on parse.com. But maybe you have a more specific question/scenario?
Guide to queries on parse.com
var query = PFQuery(className:"GameScore")
query.whereKey("playerName", equalTo:"Sean Plott")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
println(object.objectId)
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
Edit: specific version for PFUser to array of usernames
var usernames: [String]?
func loadUsernames () {
if let query = PFUser.query() {
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil { // No error - should be good to go
self.userNames = (objects as! [PFUser]).map({$0.username!})
// Call/run any code you like here - remember 'self' keyword
// It will not run until userNames is populated
self.callSomeMethod()
} else { // Error - do something clever
}
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
loadUsernames()
}