How to use expectationForNotification - ios

I've tried everything, but the only way I could get a successful test is to actually send the notification in the test function, which kinda defeats the purpose.
I have a button. When I tap the button, it sends a notification. How can I use expectationForNotification to see if this notification gets sent?
func testExample() {
let app = XCUIApplication()
let button = app.buttons["Button"]
let expectation = expectationForNotification("TEST_NOTE", object: nil) {
(notification: NSNotification!) -> Bool in
print("SUCCESS")
return true
}
button.tap()
waitForExpectationsWithTimeout(5, handler: nil)
}

it looks to me that you will have to fulfill the expectation...
func testExample()
{
let app = XCUIApplication()
let button = app.buttons["Button"]
let expectation = expectationWithDescription("waiting for the tap")
expectationForNotification("TEST_NOTE", object: nil)
{
notification in
expectation.fulfill()
return true
}
button.tap()
waitForExpectationsWithTimeout(30)
{
error in
if let e = error
{
XCTFail("\(e.debugDescription)")
}
}
}

Related

Skip Button doesn't work for google pre roll on iOS

On iOS app with GoogleInteractiveMediaAds integrated "Skip Ad" button doesn't work. Meanwhile manual call adsManager.skip() works perfectly. The button itself reacts to the tuches because it changes bounds and seems highlighted. Unfortunately, I haven't found anything according to handle tap manually, so maybe somebody has already been in this situation and could help with it.
guard
let adInformation = delegate?.latestAdInformation(), let url = adInformation.urlForIMA,
let adContainer = delegate?.videoAdDisplayContainerView()
else { return }
switch adInformation.adsType {
case .interstitials, .none:
self.play(ignoreAds: true)
return
case .prerolls, .all:
fallthrough
#unknown default:
break
}
let adDisplayContainer = IMAAdDisplayContainer(adContainer: adContainer, companionSlots: nil)
let request = IMAAdsRequest(
adTagUrl: url,
adDisplayContainer: adDisplayContainer,
contentPlayhead: contentPlayhead,
userContext: nil)
adsLoader.requestAds(with: request)
func adsLoader(_ loader: IMAAdsLoader!, adsLoadedWith adsLoadedData: IMAAdsLoadedData!) {
// Grab the instance of the IMAAdsManager and set ourselves as the delegate
adsManager = adsLoadedData.adsManager
adsManager?.delegate = self
// Create ads rendering settings and tell the SDK to use the in-app browser.
let adsRenderingSettings = IMAAdsRenderingSettings()
if let vc = delegate?.adWebControllerPreferredOpenViewController() {
adsRenderingSettings.webOpenerPresentingController = vc
}
// Initialize the ads manager.
adsManager?.initialize(with: adsRenderingSettings)
}
func adsLoader(_ loader: IMAAdsLoader!, failedWith adErrorData: IMAAdLoadingErrorData!) {
self.play(ignoreAds: true)
}
func adsManager(_ adsManager: IMAAdsManager!, didReceive event: IMAAdEvent!) {
if event.type == IMAAdEventType.LOADED {
adsManager.start()
}
}
func adsManager(_ adsManager: IMAAdsManager!, didReceive error: IMAAdError!) {
self.play(ignoreAds: true)
}
func adsManagerDidRequestContentPause(_ adsManager: IMAAdsManager!) {
self.pause(ignoreAds: true)
}
func adsManagerDidRequestContentResume(_ adsManager: IMAAdsManager!) {
self.play(ignoreAds: true)
}

NSNotification getting called multiple times

I have the following code in my didFinishLaunchingWithOptions
NotificationCenter.default.addObserver(
self,
selector: #selector(addressBookDidChange),
name: NSNotification.Name.CNContactStoreDidChange,
object: nil)
This is the method it calls
#objc func addressBookDidChange(notification: NSNotification){
self.processContacts()
}
and here is the notification being removed
func applicationWillTerminate(_ application: UIApplication) {
NotificationCenter.default.removeObserver(NSNotification.Name.CNContactStoreDidChange)
}
The problem is that when I add a new contact via the method below, addressBookDidChange gets called multiple times after, not just once
func addContact(contact:ContactObject) {
let store = CNContactStore()
let contactToAdd = CNMutableContact()
contactToAdd.givenName = contact.firstName
contactToAdd.familyName = contact.lastName
contactToAdd.organizationName = contact.company
for case let contactNumber as PhoneNumberObject in contact.phoneNumbers!{
let mobileNumber = CNPhoneNumber(stringValue: contactNumber.number)
contactToAdd.phoneNumbers.append(CNLabeledValue(label: contactNumber.type.getCNLabelValue(), value: mobileNumber))
}
if let image = contact.image {
contactToAdd.imageData = UIImagePNGRepresentation(image)
}
let saveRequest = CNSaveRequest()
saveRequest.add(contactToAdd, toContainerWithIdentifier: nil)
do {
try store.execute(saveRequest)
} catch {
NSLog("Error adding contact \(contact.firstName) \(contact.lastName) : \(error)")
}
}
How can I make the notification be just called once for an addition of one contact?
I believe that it is not good idea to set notification posting based on delegate and so what should be doing is post notification from delegate checking condition whether change in notification is contact is added.

unexpected non void return value in void function

I've been pouring over stack overflow for ages trying to find a way out of this error:
unexpected non void return value in void function
that I am getting with returning a Bool within my function.
i just can't seem to dig my way out of this one. I'm sure its something to do with async but I'm not very familiar with these types of functions.
class CheckReachability {
class func setupReachability (hostName:String?, useClosures: Bool) -> Bool{
var reachability : Reachability!
var connected = false
let reachability2 = hostName == nil ? Reachability() : Reachability(hostname: hostName!)
reachability = reachability2
try! reachability?.startNotifier()
if useClosures {
reachability2?.whenReachable = { reachability in
DispatchQueue.main.async {
connected = true
print("Reachable....")
}
}
reachability2?.whenUnreachable = { reachability in
DispatchQueue.main.async {
connected = false
print("Not Connected....")
}
}
} else {
NotificationCenter.default.addObserver(self, selector: Selector(("reachabilityChanged:")), name: ReachabilityChangedNotification, object: reachability2)
}
return connected
}
}
calling this from viewdidload on another vc doesn't allow enough time to get a true result
let connected = CheckReachability.setupReachability(hostName: nil, useClosures: true)
if connected {
Your question is confusing because the code you posted does not have the error you describe. However, you're trying to create a function that returns a result from an async function. That is not how async works.
Async functions start to do a task in the background, where that task won't be finished before it's time to return.
You need to adjust your thinking. Instead of trying to return a result from your function, you need to write your function to take a completion handler. You then call the completion handler once the long-running task has finished (which is after your function has returned.)
#bubuxu provided you with code showing how to modify your function as I described.
If you want to write a checking class to listen to the reachability, define it as a singleton and pass the completeBlock to it like this:
class CheckReachability {
static let shared = CheckReachability()
var reachability: Reachability?
func setupReachability(hostName:String?, completeBlock: ((Bool) -> Void)? = nil) {
reachability = hostName == nil ? Reachability() : Reachability(hostname: hostName!)
try? reachability?.startNotifier()
if let block = completeBlock {
reachability?.whenReachable = { reachability in
DispatchQueue.main.async {
print("Reachable....")
block(true)
}
}
reachability?.whenUnreachable = { reachability in
DispatchQueue.main.async {
print("Not Connected....")
block(false)
}
}
} else {
// If we don't use block, there is no point to observe it.
NotificationCenter.default.addObserver(self, selector: #selector(reachabilityChanged(_:)), name: .ReachabilityChangedNotification, object: nil)
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
#objc func reachabilityChanged(_ notification: Notification) {
// ?? what should we do here?
}
}

GKLocalPlayer.authenticated always returns false

I'm using Game Center in my app and I'm having some problem with the GKLocalPlayer.authenticated attribute. Regardless if the authentication process is successful or not, localPlayer.authenticated always returns false. This also happens if my device is already logged in to Game Center.
I get this both on actual device (iPhone 6s) and simulator (tried several).
The only information I've found about this suggests that there is a problem with the time settings but they seem to be fine.
Is this a bug or am I doing something wrong?
private static let localPlayer = GKLocalPlayer()
static func authenticateLocalPlayer() {
localPlayer.authenticateHandler = { (viewController, error) -> Void in
if let viewController = viewController {
if let rootViewController = UIApplication.sharedApplication().keyWindow?.rootViewController {
rootViewController.presentViewController(viewController, animated: true, completion: nil)
}
} else if localPlayer.authenticated {
gameCenterEnabled = true
let defaultCenter = NSNotificationCenter.defaultCenter()
defaultCenter.postNotificationName("local_player_authenticated", object: nil)
} else {
gameCenterEnabled = false
}
if let error = error {
print(error)
}
}
}
static func isAuthenticated() -> Bool {
return localPlayer.authenticated
}
My bad, looks like I made a little mistake when translating this code from Objective-C. It's supposed to be
GKLocalPlayer.localPlayer()
not
GKLocalPlayer()

How to handle touchID when loading app from background Swift

I'm implementing the login possibility with touchID using Swift.
Following: when the App is started, there is a login screen and a touchID popup - that's working fine. The problem occurs, when the app is loaded from background: I want the touchID popup appear over a login screen if a specific timespan hasn't been exceeded yet - but this time I want the touchID to go to the last shown view before the app entered background. (i.e. if the user wants to cancel the touchID, there is a login screen underneath where he then can authenticate via password, which leads him to the last shown view OR if the touchID authentication succeeded, the login screen should be dismissed and the last shown view presented.)
I really tried everything on my own, and searched for answers - nothing did help me. Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
//notify when foreground or background have been entered -> in that case there are two methods that will be invoked: willEnterForeground and didEnterBackground
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "willEnterForeground", name:UIApplicationWillEnterForegroundNotification, object: nil)
notificationCenter.addObserver(self, selector: "didEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil)
password.secureTextEntry = true
if (username != nil) {
username.text = "bucketFit"
}
username.delegate = self
password.delegate = self
if let alreadyShown : AnyObject? = def.objectForKey("alreadyShown") {
if (alreadyShown == nil){
authenticateWithTouchID()
}
}
}
willEnterForeground:
func willEnterForeground() {
//save locally that the guide already logged in once and the application is just entering foreground
//the variable alreadyShown is used for presenting the touchID, see viewDidAppear method
def.setObject(true, forKey: "alreadyShown")
if let backgroundEntered : AnyObject? = def.objectForKey("backgroundEntered") {
let startTime = backgroundEntered as! NSDate
//number of seconds the app was in the background
let inactivityDuration = NSDate().timeIntervalSinceDate(startTime)
//if the app was longer than 3 minutes inactiv, ask the guide to input his password
if (inactivityDuration > 2) {
showLoginView()
} else {
def.removeObjectForKey("alreadyShown")
showLoginView()
}
}
}
authenticateWithTouchID():
func authenticateWithTouchID() {
let context : LAContext = LAContext()
context.localizedFallbackTitle = ""
var error : NSError?
let myLocalizedReasonString : NSString = "Authentication is required"
//check whether the iphone has the touchID possibility at all
if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error) {
//if yes then execute the touchID and see whether the finger print matches
context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString as String, reply: { (success : Bool, evaluationError : NSError?) -> Void in
//touchID succeded -> go to students list page
if success {
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
self.performSegueWithIdentifier("studentsList", sender: self)
})
} else {
// Authentification failed
print(evaluationError?.description)
//print out the specific error
switch evaluationError!.code {
case LAError.SystemCancel.rawValue:
print("Authentication cancelled by the system")
case LAError.UserCancel.rawValue:
print("Authentication cancelled by the user")
default:
print("Authentication failed")
}
}
})
}
}
shouldPerformSegueWithIdentifier:
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
if (false) { //TODO -> username.text!.isEmpty || password.text!.isEmpty
notify("Login failed", message: "Please enter your username and password to proceed")
return false
} else if (false) { //TODO when backend ready! -> !login("bucketFit", password: "test")
notify("Incorrect username or password", message: "Please try again")
return false
//if the login page is loaded after background, dont proceed (then we need to present the last presented view on the stack before the app leaved to background)
} else if let alreadyShown : AnyObject? = def.objectForKey("alreadyShown") {
if (alreadyShown != nil){
//TODO check whether login data is correct
dismissLoginView()
return false
}
}
return true
}
Thank you in advance.
What you could do is create a AuthenticationManager. This manager would be a shared instance which keep track of whether authentication needs to be renewed. You may also want this to contain all of the auth methods.
class AuthenticationManager {
static let sharedInstance = AuthenticationManager()
var needsAuthentication = false
}
In AppDelegate:
func willEnterForeground() {
def.setObject(true, forKey: "alreadyShown")
if let backgroundEntered : AnyObject? = def.objectForKey("backgroundEntered") {
let startTime = backgroundEntered as! NSDate
//number of seconds the app was in the background
let inactivityDuration = NSDate().timeIntervalSinceDate(startTime)
//if the app was longer than 3 minutes inactiv, ask the guide to input his password
if (inactivityDuration > 2) {
AuthenticationManager.sharedInstance.needsAuthentication = true
}
}
}
Then, subclass UIViewController with a view controller named SecureViewController. Override viewDidLoad() in this subclass
override fun viewDidLoad() {
super.viewDidLoad()
if (AuthenticationManager.sharedInstance().needsAuthentication) {
// call authentication methods
}
}
Now, make all your View Controllers that require authentication subclasses of SecureViewController.

Resources