I am writing a test framework for an ios app that requires importing image from photos/gallery app for validation. I am using XCTest Framework for testing. I have looked over the Internet for some resources but couldn't find any. Can anyone help me how to approach the problem. Again, I have pick the image not from inside the app but from image but from Photos library.
Yes
You can access the photo library but it require XCUITest and the recorder doesn't work inside of Apple's UIRemoteView's like the photo picker. What you have to do is get to the photo picker inside an XCUITest, set a breakpoint, then inspect the view hierarchy to find the elements that are able to be navigated with XCUITest. The example below works with the pictures that come with the simulator.
let app = XCUIApplication()
// get to the photo library
// set a breakpoint, po [[UIWindow keyWindow] recursiveDescription]
let tablesQuery = app.tables
app.sheets.buttons["Choose From Library"].tap()
app.cells["Camera Roll"].tap()
app.cells["Photo, Landscape, March 12, 2011, 7:17 PM"].tap()
let photosApp = XCUIApplication(bundleIdentifier: "com.apple.mobileslideshow")
photosApp.launch()
let continueButton = photosApp.buttons["Continue"]
if continueButton.waitForExistence(timeout: 2) {
continueButton.tap()
}
photosApp.collectionViews["PhotosGridView"].cells.firstMatch.tap()
This works reliably for me, derived from Steve Moser's helpful answer:
/*
LIKE: The left hand expression equals the right-hand expression: ? and * are allowed as wildcard characters, where ? matches 1 character and * matches 0 or more characters.
Resource: https://nshipster.com/nspredicate/
*/
let yellowLeafGreenBackground = NSPredicate(format: "label LIKE 'Photo, October 09, 2009, *:09*'")
let waterfallCloseUpOverRocks = NSPredicate(format: "label LIKE 'Photo, August 08, 2012, *:29*'")
let treeWithWaterfallBackground = NSPredicate(format: "label LIKE 'Photo, August 08, 2012, *:52*'")
let yellowFlowerSucculentBeach = NSPredicate(format: "label LIKE 'Photo, March 12, 2011, *:17*'")
let amazingWaterfall = NSPredicate(format: "label LIKE 'Photo, August 08, 2012, *:55*'")
// take note with this one, it is an HDR image
let beautyFlowers = NSPredicate(format: "label LIKE 'Photo, March 30, 2018, *:14*'")
func testTappingOnSpecificImage() {
// ... test setup and navigation to get to presented Camera Roll (PHPicker or UIImagePickerController)
// trigger presentation of camera roll
app.buttons["Choose from Photos"].tap()
XCTAssertTrue(app.buttons["Cancel"].waitForExistence(timeout: 3))
let activeQuery = treeWithWaterfallBackground
app.scrollViews.otherElements.images.matching(activeQuery).element.tap()
}
You can see the dates for the individual photos by going into the Photos app on the simulator and tapping into the detail view for a specific image. Like Mikkel Selsøe pointed out, the timestamps are localized for the current time zone.
Available in a gist here: https://gist.github.com/bitops/9182b5ee96c682aba57d9fa16ca6b987
XCTest provides special method just for that case.
let galleryAccessMonitor = addUIInterruptionMonitor(withDescription: "Intercept Gallery Access") { alert -> Bool in
alert.buttons.element(boundBy: 1).tap() /// tap accept
return true /// mark as handled
}
see https://useyourloaf.com/blog/handling-system-alerts-in-ui-tests/
Xcode 14.1 with iOS 16.1 simulator solution:
let app = XCUIApplication()
app.scrollViews.otherElements.images.containing(NSPredicate(format: "label BEGINSWITH 'Photo'")).element(boundBy: 0).tap()
Here's my solution that is independent on the photos being added to the library:
let app = XCUIApplication()
app.launch()
app.buttons["add.photo.button"].tap()
let photosNavBar = app.navigationBars["Photos"]
if photosNavBar.waitForExistence(timeout: 2) {
XCTAssert(app.navigationBars["Photos"].exists)
} else {
XCTFail()
}
If you do not care which image you want to choose then this worked for me:
let image = app.scrollViews.images.matching(NSPredicate(format: "label LIKE '*2012*'")).firstMatch
if image.waitForExistence(timeout: 5) {
image.tap()
}
Note that similar answers which use full name of one of the default photos tend to fail in many cases because for example device/simulator language can be different than english and then image names do not start with "Photo" word, also according to other answers it seems that the time in those names can also be different so that is why I used only year in above code.
You can't interact with an app outside your own app using XCTest. The tests have a reference to your app's bundle identifier, and that is the only app they are able to interact with.
XCTest requires a certain amount of access to the internals of your app in order to give you information about it to allow you to interact with it through XCTest, and this is not something that is available to access in apps that you did not make yourself.
Related
Our application has had ApplePay implemented for a number of years. Just recently I hit the button to trigger it to only find out the pay sheet from PKPaymentAuthorizationViewController doesn't appear. It won't slide up in a sandbox (i.e. simulator or device connected to Xcode) environment, but putting a breakpoint shows that it is being created successfully. It's been awhile since I've tested this, but I am suspecting something change with Xcode11 or iOS 13.
My code is pretty standard Apple Pay, but posted below.
let item = PKPaymentSummaryItem()
item.label = "Our Label"
let price = NSDecimalNumber(mantissa: UInt64(totalPrice), exponent: -2, isNegative: false)
item.amount = price
items.append(item)
request.paymentSummaryItems = items
if let applePayController = PKPaymentAuthorizationViewController(paymentRequest: request) {
applePayController.delegate = self
present(applePayController, animated: true)
}
This will happen if the merchantID used by your PKPaymentRequest is not listed in mobile.entitlements. Or if the MerchantID is not enabled by your app's provisioning profile.
View Controller
PKPaymentRequest *request = [[PKPaymentRequest alloc] init];
request.merchantIdentifier = "com.your.app.merchandId"
mobile.entitlements
<dict>
<key>com.apple.developer.in-app-payments</key>
<array>
<string>com.your.app.merchandId</string>
</array>
</dict>
I was facing the same issue, where Apple Pay suddenly stopped working in our app. We've also been supporting this for at least halve a year now.
Using the latest Xcode (Xcode 11.2, Beta 2 (11B44)), it seems to work again.
I'm guessing it was a Xcode bug then. Even though it's not listed in Xcode's release notes for Xcode 11.1 or Xcode 11.2 beta 2.
I have pasted your snippet into my app, which uses ApplePay and I have run it on a test device which is sandboxed and has ApplePay enabled and the ApplePay button didn't work.
What I found it quite surprising.
When you look at the docs for PKPaymentSummaryItem it has only two initialisers:
init(label: String, amount: NSDecimalNumber)
init(label: String, amount: NSDecimalNumber, type:PKPaymentSummaryItemType)
And neither has default values, but you are initialising one with an empty initialiser. Surprisingly there are no errors or warnings and it even comes up in code completion.
So I changed the initialiser:
let item = PKPaymentSummaryItem(label: "Our Label",
amount: NSDecimalNumber(mantissa: UInt64(totalPrice),
exponent: -2,
isNegative: false))
request.paymentSummaryItems = [items]
if let applePayController = PKPaymentAuthorizationViewController(paymentRequest: request) {
applePayController.delegate = self
present(applePayController, animated: true)
}
And it works (on device only - not simulator)!
That made my wonder so I just changed your item from let to var and it works as well. I would still go with the documented initialiser.
I'm trying to share a story with a background image a a sticker image via URL Scheme on my ios app, i am using the attached code and it dose not work.
When i'm trying to share just a background image or just a sticker it does work. But when im trying share both a background image and a sticker in top of it, it dose not work.
Any Ideas?
func shareToInstagram(deepLinkString : String){
let url = URL(string: "instagram-stories://share")!
if UIApplication.shared.canOpenURL(url){
let backgroundData = UIImageJPEGRepresentation(UIImage(named: "shop_placeholder")!, 1.0)!
let creditCardImage = UIImage(named: "share_instagram")!
let stickerData = UIImagePNGRepresentation(creditCardImage)!
let pasteBoardItems = [
["com.instagram.sharedSticker.backgroundImage" : backgroundData],
["com.instagram.sharedSticker.stickerImage" : stickerData],
]
if #available(iOS 10.0, *) {
UIPasteboard.general.setItems(pasteBoardItems, options: [.expirationDate: Date().addingTimeInterval(60 * 5)])
} else {
UIPasteboard.general.items = pasteBoardItems
}
UIApplication.shared.openURL(url)
}
I copy pasted OP's code for use in my own app (only substituting different UIImages) and found only 1 issue, pasteboard items should be contained in a single array otherwise instagram will render only the first item (in this case the background layer). To fix this, replace the declaration of pasteboard items with the following code
let pasteBoardItems = [
["com.instagram.sharedSticker.backgroundImage" : backgroundData,
"com.instagram.sharedSticker.stickerImage" : stickerData]
]
(basically just remove the close and open bracket separating the two items)
Also as a previous answer stated, make sure "instagram-stories" is included in LSApplicationQueriesSchemes in the info.plist file
I use this exact code in my app and it now works perfect
Everything is correct, my code is similar and it works for iOS 11+. I suggest you the following:
check the image data you pass to pasteboard (jpg can't be converted with
UIImagePNGRepresentation and vice versa)
check the info.plist. You should enable "instagram-stories" scheme in it (LSApplicationQueriesSchemes key)
Like Alec said, you need to put all of Instagram data in one list, not multiple lists. look at the example from the meta documents:
NSArray *pasteboardItems = #[#{#"com.instagram.sharedSticker.stickerImage" : stickerImage,
#"com.instagram.sharedSticker.backgroundTopColor" : backgroundTopColor,
#"com.instagram.sharedSticker.backgroundBottomColor" : backgroundBottomColor}];
2. For more recent readers, as of swift 4.2 and iOS 12 UIImageJPEGRepresentation is replaced by jpegData. change
let backgroundData = UIImageJPEGRepresentation(yourImage, 1.0)
with
let backgroundData = yourImage.jpegData(compressionQuality: 1.0)
I am developing an iOS application which supports English and Arabic. User can change the application language from inside the app.
When user changes the language I am setting it like ,
//change app language
UserDefaults.standard.set([language], forKey: "AppleLanguages")
currentLanguage = language
UserDefaults.standard.synchronize()
//current language updating
var currentLanguage : String{
get{
if let selectedLanguage = UserDefaults.standard.string(forKey: "selectedLanguage"){
return selectedLanguage
}else{
let language = Locale.preferredLanguages[0]
if language.hasPrefix("ar"){
return SupportedLanguage.ar.rawValue
}else{
return SupportedLanguage.en.rawValue
}
}
}
set{
UserDefaults.standard.setValue(newValue, forKey: "selectedLanguage")
}
}
In this way, App is not exiting. Just reloading the root view controller
The issue I am facing is, when I change the application language like this, the privacy alerts like “..requesting permission for using Location”, “… would ,like to use Photo album” etc are not showing in the selected language. I have created InfoPlist.string files for English and Arabic and added like
NSCameraUsageDescription = ".... would like to access Camera";
NSLocationAlwaysAndWhenInUseUsageDescription = ".... wants to use your current location for better usability";
Still its not showing. Also I tried deleting, cleaning app, deleting derived data.
Any idea why its happening?
Changing AppleLanguages key needs app to be restarted so new localization applied , you can try to use NSLocalizedString with tableName or change current bundle you read from , but system localization won't be changed until app restarted
ios does not let the containing app and the contained extensions to share a common container, so UserDefaults is the proposed solution.
I have tried using UserDefaults with sirikit intent handler assuming the handler behaves as an extension as follows :
inside IntentHandler.swift
let shared = UserDefaults(suiteName:XXXXXXXX.group...)
shared?.set("saved value 1", forKey: "key1")
shared?.set("saved value 2", forKey: "key2")
shared?.set("saved value 3", forKey: "key3")
inside ViewController.swift in viewDidLoad
let shared = UserDefaults(suiteName:XXXXXXXX.group...)
if let temp1 = shared?.string(forKey:"key1")
{
contentLabel.text = temp1
}
if let value = shared?.string(forKey: "key2")
{
valueLabel.text = value
}
if let key = shared?.string(forKey: "key3")
{
keyLabel.text = key
}
i can see the strings corresponding to key1 and key2 on my ipad screen but not for key3, peppering the code with synchronizes does not help.
here are my questions :
1) are sirikit handlers different from other extensions? if yes how to pass data to my app? if not am i using UserDefaults incorrectly?
2) is there a better way to handle IPC between the app and its extensions where i just need to pass simple string messages between them.
using swift 3.0 and xcode 8.2.1
Check that you have the App Group enabled for all targets you want to access the group from. Check in project -> your target -> capabilities under "App Groups".
There's something called MMWomhole. It will definitely do the work.
i have an app with ios 8, swift and CoreData.
all works fine :)
now i would like to add an today extion - this is my first time to work with today extions. i very proud to say, that i already can set in my app an NSUserdafault value and show it on my today extension :)
but now i would like to go a step forward.
in my app i have a CoreData Entiny LM_ITEMS with an Attribute "Hersteller"
how can i list up all data of LM_ITEMS in my today extension?
Thank you :)
in my app i do it like this way:
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var LM = [LM_ITEMS]()
func DatenAbrufen() {
let fetchRequest = NSFetchRequest(entityName: "LM_ITEMS")
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LM_ITEMS] {
LM = fetchResults
}
}
But this doesnt work in today extension ...
You should better create a core data stack singleton object and include it in both targets, your app's target and extension target.
What #Lucian said is correct and can become very useful if you want to add other targets.
However if you want access the same database of your main app you have to make both the app and the widget use the same App Group in the target capabilities and place your core data database in the shared container.