#if Macro in swift to checking device - swift

i am defining the macro at my Appdelegate , here is my code
let IPHONE = UIDevice.currentDevice().userInterfaceIdiom == .Phone
let IPAD = UIDevice.currentDevice().userInterfaceIdiom == .Pad
#if IPHONE
let LEFT_DRAWER_HEIGHT : CGFloat = 270.0
#elseif IPAD
let LEFT_DRAWER_HEIGHT : CGFloat = 404.0
#else
let LEFT_DRAWER_HEIGHT : CGFloat = 404.0
#endif
but it always returns LEFT_DRAWER_HEIGHT = 404.0 rather i have run in iphone device. whats the mistake? can anyone figure it out.

Do you have separate targets for iPhone and iPad? And are you defining those constants in each of those targets?
#if is a compile time conditional, so if you have a universal app, the compiler can't know at compile time what device will the app run on. You should instead use a normal if condition.

Related

How can I get the last window using UIWindowScene.windows in iOS 15?

Going off this post which describes how to get the first window - has definitely helped me learned more about this spec, but it wasn't able to get me the solution I need. How to resolve: 'keyWindow' was deprecated in iOS 13.0
UIApplication.shared.windows.last works like a charm, on iOS 13, 14 and 15. However, it's depreciated in iOS 15 and I am adding an #available statement to make sure it works properly in the future, etc.
Moreover, this is my code and I cannot seem to achieve showing the view I am displaying on top of the UIKeyboard...
if #available(iOS 15.0, *) {
let scenes = UIApplication.shared.connectedScenes.first as? UIWindowScene
let window = scenes?.windows.last
if let window = window {
layer.zPosition = CGFloat(MAXFLOAT)
frame = window.bounds
window.addSubview(self)
}
} else {
if let window = UIApplication.shared.windows.last {
///
layer.zPosition = CGFloat(Float.greatestFiniteMagnitude)
frame = window.bounds
window.addSubview(self)
}
}
To reiterate;
This code works on iOS 13 & iOS 15:
if let window = UIApplication.shared.windows.last {
layer.zPosition = CGFloat(Float.greatestFiniteMagnitude)
frame = window.bounds
window.addSubview(self)
}
This would be fine. However, as of iOS 15, UIApplication.shared.windows has been depreciated. The note in UIApplication.h states: 'Use UIWindowScene.windows on a relevant window scene instead.'
Moreover, this code does NOT work on iOS 15.0:
if #available(iOS 15.0, *) {
let scenes = UIApplication.shared.connectedScenes.first as? UIWindowScene
let window = scenes?.windows.last
if let window = window {
layer.zPosition = CGFloat(MAXFLOAT)
frame = window.bounds
window.addSubview(self)
}

How to change the size of modal or sheet in SwiftUI?

How to change the size of modal or sheet in SwiftUI? Just like the ones in the pictures. I noticed that the AirDrop options appear as a full-screen sheet on iPhones but on iPads that is not the case (as you can see).
I'm creating an app with a login form, this size personalization would be useful to optimize the size of the device because most of the space isn't used.
Is there a way to change the size of those components?
From your description, I didn't really understand what do you want to achieve, but this enum will help you to determine what device is currently running. You can build a logic of what do you want to present around a simple if else statement:
if DeviceTypes.isiPad {
// logic for all iPads
} else {
// logic for other devices
}
And here is Enum:
enum DeviceTypes {
enum ScreenSize {
static let width = UIScreen.main.bounds.size.width
static let height = UIScreen.main.bounds.size.height
static let maxLength = max(ScreenSize.width, ScreenSize.height)
}
static let idiom = UIDevice.current.userInterfaceIdiom
static let isiPad = idiom == .pad && ScreenSize.maxLength >= 1024.0
}

UITest tap not working on iOS 12.4 using iPhone X (works on iPhone 7)

Could this be related to SafeArea issues in iOS12.4 (and actually 12.2 as well)?
I use the following function to tap a view during my UITests.
func tapAtLocation(_ element: XCUIElement) -> Self {
let loc = element.coordinate(withNormalizedOffset: .init(dx: 0, dy: 0))
loc.tap()
return self
}
I try to tap at the location of a specific image.
So I get the image and trigger a tap
let myImage = App.images[myImageViewIdentifier].firstMatch
tapAtLocation(myImage)
It works on new iOS versions and also on iPhone 7 iOS 12.4 but not on iPhone X.
And I need it to work on iPhone X :)
What do you propose me to do? Maybe you have a nice debugging trick to see exactly where it tries to tap ?
ok ! After a day of fail and retry, a simple solution worked out!
func tapAtLocation(_ element: XCUIElement) -> Self {
let loc = element.coordinate(withNormalizedOffset: .init(dx: 0.5, dy: 0.5))
loc.tap()
return self
}
This works on all devices (iPhone X, 7 and 11) with iOS 12.4 and 13+

Use Front Camera for AVCaptureDevice Preview Layer Automatically Like Snapchat or Houseparty (Swift 3) [duplicate]

This question already has answers here:
How to get the front camera in Swift?
(8 answers)
Closed 6 years ago.
Essentially what I'm trying to accomplish is having the front camera of the AVCaptureDevice be the first and only option on a application during an AVCaptureSession.
I've looked around StackOverflow and all the methods and answers provided are deprecated as of iOS 10, Swift 3 and Xcode 8.
I know you're supposed to enumerate the devices with AVCaptureDeviceDiscoverySession and look at them to distinguish front from back, but I'm unsure of how to do so.
Could anyone help? It would amazing if so!
Here's my code:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
previewLayer.frame = singleViewCameraSlot.bounds
self.singleViewCameraSlot.layer.addSublayer(previewLayer)
captureSession.startRunning()
}
lazy var captureSession: AVCaptureSession = {
let capture = AVCaptureSession()
capture.sessionPreset = AVCaptureSessionPreset1920x1080
return capture
}()
lazy var previewLayer: AVCaptureVideoPreviewLayer = {
let preview = AVCaptureVideoPreviewLayer(session: self.captureSession)
preview?.videoGravity = AVLayerVideoGravityResizeAspect
preview?.connection.videoOrientation = AVCaptureVideoOrientation.portrait
preview?.bounds = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height)
preview?.position = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY)
return preview!
}()
func setupCameraSession() {
let frontCamera = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo) as AVCaptureDevice
do {
let deviceInput = try AVCaptureDeviceInput(device: frontCamera)
captureSession.beginConfiguration()
if (captureSession.canAddInput(deviceInput) == true) {
captureSession.addInput(deviceInput)
}
let dataOutput = AVCaptureVideoDataOutput()
dataOutput.videoSettings = [(kCVPixelBufferPixelFormatTypeKey as NSString) : NSNumber(value: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange as UInt32)]
dataOutput.alwaysDiscardsLateVideoFrames = true
if (captureSession.canAddOutput(dataOutput) == true) {
captureSession.addOutput(dataOutput)
}
captureSession.commitConfiguration()
let queue = DispatchQueue(label: "io.goodnight.videoQueue")
dataOutput.setSampleBufferDelegate(self, queue: queue)
}
catch let error as NSError {
NSLog("\(error), \(error.localizedDescription)")
}
}
If you just need to find a single device based on simple characteristics (like a front-facing camera that can shoot video), just use AVCaptureDevice.default(_:for:position:). For example:
guard let device = AVCaptureDevice.default(.builtInWideAngleCamera,
for: .video,
position: .front)
else { fatalError("no front camera. but don't all iOS 10 devices have them?")
// then use the device: captureSession.addInput(device) or whatever
Really that's all there is to it for most use cases.
There's also AVCaptureDeviceDiscoverySession as a replacement for the old method of iterating through the devices array. However, most of the things you'd usually iterate through the devices array for can be found using the new default(_:for:position:) method, so you might as well use that and write less code.
The cases where AVCaptureDeviceDiscoverySession is worth using are the less common, more complicated cases: say you want to find all the devices that support a certain frame rate, or use key-value observing to see when the set of available devices changes.
By the way...
I've looked around StackOverflow and all the methods and answers provided are deprecated as of iOS 10, Swift 3 and Xcode 8.
If you read Apple's docs for those methods (at least this one, this one, and this one), you'll see along with those deprecation warnings some recommendations for what to use instead. There's also a guide to the iOS 10 / Swift 3 photo capture system and some sample code that both show current best practices for these APIs.
If you explicitly need the front camera, you can use AVCaptureDeviceDiscoverySession as specified here.
https://developer.apple.com/reference/avfoundation/avcapturedevicediscoverysession/2361539-init
This allows you to specify the types of devices you want to search for. The following (untested) should give you the front facing camera.
let deviceSessions = AVCaptureDeviceDiscoverySession(deviceTypes: [AVCaptureDeviceType.builtInWideAngleCamera], mediaType: AVMediaTypeVideo, position: AVCaptureDevicePosition.front)
This deviceSessions has a devices property which is an array of AVCaptureDevice types containing only the devices matching that search criteria.
deviceSessions?.devices
That should be either 0 or 1 depending on if the device has a front facing camera or not (some iPods won't for example).

Is there a way to reset the app between tests in Swift XCTest UI?

Is there an API call within XCTest that I can put into the setUP() or tearDown() to reset the app between tests? I looked in the dot syntax of XCUIApplication and all I saw was the .launch()
OR is there a way to call a shell script in Swift? I could then call xcrun in-between test methods to reset the simulator.
You can add a "Run Script" phase to build phases in your test target to uninstall the app before running unit tests against it, unfortunately this is not between test cases, though.
/usr/bin/xcrun simctl uninstall booted com.mycompany.bundleId
Update
Between tests, you can delete the app via the Springboard in the tearDown phase. Although, this does require use of a private header from XCTest. (Header dump is available from Facebook's WebDriverAgent here.)
Here is some sample code from a Springboard class to delete an app from Springboard via tap and hold:
#Swift 4:
import XCTest
class Springboard {
static let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
/**
Terminate and delete the app via springboard
*/
class func deleteMyApp() {
XCUIApplication().terminate()
// Force delete the app from the springboard
let icon = springboard.icons["Citizen"]
if icon.exists {
let iconFrame = icon.frame
let springboardFrame = springboard.frame
icon.press(forDuration: 1.3)
// Tap the little "X" button at approximately where it is. The X is not exposed directly
springboard.coordinate(withNormalizedOffset: CGVector(dx: (iconFrame.minX + 3) / springboardFrame.maxX, dy: (iconFrame.minY + 3) / springboardFrame.maxY)).tap()
springboard.alerts.buttons["Delete"].tap()
}
}
}
#Swift 3-:
import XCTest
class Springboard {
static let springboard = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.springboard")
/**
Terminate and delete the app via springboard
*/
class func deleteMyApp() {
XCUIApplication().terminate()
// Resolve the query for the springboard rather than launching it
springboard.resolve()
// Force delete the app from the springboard
let icon = springboard.icons["MyAppName"]
if icon.exists {
let iconFrame = icon.frame
let springboardFrame = springboard.frame
icon.pressForDuration(1.3)
if #available(iOS 13.0, *) {
springboard.buttons["Remove App"].tap()
springboard.alerts.buttons["Delete App"].tap()
springboard.alerts.buttons["Delete"].tap()
} else {
// Tap the little "X" button at approximately where it is. The X is not exposed directly
let xPosition = CGVector(dx: (iconFrame.minX + 3) / springboardFrame.maxX,
dy: (iconFrame.minY + 3) / springboardFrame.maxY)
springboard.coordinate(withNormalizedOffset: xPosition).tap()
springboard.alerts.buttons["Delete"].tap()
}
}
}
}
And then:
override func tearDown() {
Springboard.deleteMyApp()
super.tearDown()
}
The private headers were imported in the Swift bridging header. You'll need to import:
// Private headers from XCTest
#import "XCUIApplication.h"
#import "XCUIElement.h"
Note: As of Xcode 10, XCUIApplication(bundleIdentifier:) is now exposed by Apple, and the private headers are no longer needed.
At this time, the public API provided by Xcode, the Simulator and the Swift Package Manager does not appear have any method callable from setUp() and tearDown() XCText subclasses to "Reset Contents and Settings" for the simulator.
There are other possible approaches which use public APIs:
Application Code. Add some myResetApplication() application code to put the application in a known state. However, device (simulator) state control is limited by the application sandbox ... which is not much help outside the application. This approach is OK for clearing application controllable persistance.
Shell Script. Run the tests from a shell script. Use xcrun simctl erase all or xcrun simctl uninstall <device> <app identifier> or similar between each test run to reset the simulator (or uninstall the app). see StackOverflow: "How can I reset the iOS Simulator from the command line?"
xcrun simctl --help
# Uninstall a single application
xcrun simctl uninstall --help
xcrun simctl uninstall <device> <app identifier>
# Erase a device's contents and settings.
xcrun simctl erase <device>
xcrun simctl erase all # all existing devices
# Grant, revoke, or reset privacy and permissions
simctl privacy <device> <action> <service> [<bundle identifier>]
Xcode Schema Script Action. Add xcrun simctl erase all (or xcrun simctl erase <DEVICE_UUID>) or similar commands to an Xcode Scheme section such as the Test or Build section. Select the Product > Scheme > Edit Scheme… menu. Expand the Scheme Test section. Select Pre-actions under the Test section. Click (+) add "New Run Script Action". The command xcrun simctl erase all can be typed in directly without requiring any external script.
Options for invoking 1. Application Code to reset the application:
A. Application UI. [UI Test] Provide a reset button or other UI action which resets the application. The UI element can be exercised via XCUIApplication in XCTest routines setUp(), tearDown() or testSomething().
B. Launch Parameter. [UI Test] As noted by Victor Ronin, an argument can be passed from the test setUp() ...
class AppResetUITests: XCTestCase {
override func setUp() {
// ...
let app = XCUIApplication()
app.launchArguments = ["MY_UI_TEST_MODE"]
app.launch()
... to be received by the AppDelegate ...
class AppDelegate: UIResponder, UIApplicationDelegate {
func application( …didFinishLaunchingWithOptions… ) -> Bool {
// ...
let args = ProcessInfo.processInfo.arguments
if args.contains("MY_UI_TEST_MODE") {
myResetApplication()
}
C. Xcode Scheme Parameter. [UI Test, Unit Test] Select the Product > Scheme > Edit Scheme… menu. Expand the Scheme Run section. (+) Add some parameter like MY_UI_TEST_MODE. The parameter will be available in ProcessInfo.processInfo.
// ... in application
let args = ProcessInfo.processInfo.arguments
if args.contains("MY_UI_TEST_MODE") {
myResetApplication()
}
D. Direct Call. [Unit Test] Unit Test Bundles are injected into the running application and can directly call some myResetApplication() routine in the application. Caveat: Default unit tests run after the main screen has loaded. see Test Load Sequence However, UI Test Bundles runs as a process external to the application under test. So, what works in the Unit Test gives a link error in a UI Test.
class AppResetUnitTests: XCTestCase {
override func setUp() {
// ... Unit Test: runs. UI Test: link error.
myResetApplication() // visible code implemented in application
Updated for swift 3.1 / xcode 8.3
create bridging header in test target:
#import <XCTest/XCUIApplication.h>
#import <XCTest/XCUIElement.h>
#interface XCUIApplication (Private)
- (id)initPrivateWithPath:(NSString *)path bundleID:(NSString *)bundleID;
- (void)resolve;
#end
updated Springboard class
class Springboard {
static let springboard = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.springboard")!
static let settings = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.Preferences")!
/**
Terminate and delete the app via springboard
*/
class func deleteMyApp() {
XCUIApplication().terminate()
// Resolve the query for the springboard rather than launching it
springboard.resolve()
// Force delete the app from the springboard
let icon = springboard.icons["{MyAppName}"] /// change to correct app name
if icon.exists {
let iconFrame = icon.frame
let springboardFrame = springboard.frame
icon.press(forDuration: 1.3)
// Tap the little "X" button at approximately where it is. The X is not exposed directly
springboard.coordinate(withNormalizedOffset: CGVector(dx: (iconFrame.minX + 3) / springboardFrame.maxX, dy: (iconFrame.minY + 3) / springboardFrame.maxY)).tap()
springboard.alerts.buttons["Delete"].tap()
// Press home once make the icons stop wiggling
XCUIDevice.shared().press(.home)
// Press home again to go to the first page of the springboard
XCUIDevice.shared().press(.home)
// Wait some time for the animation end
Thread.sleep(forTimeInterval: 0.5)
let settingsIcon = springboard.icons["Settings"]
if settingsIcon.exists {
settingsIcon.tap()
settings.tables.staticTexts["General"].tap()
settings.tables.staticTexts["Reset"].tap()
settings.tables.staticTexts["Reset Location & Privacy"].tap()
settings.buttons["Reset Warnings"].tap()
settings.terminate()
}
}
}
}
Solution for iOS 13.2
final class Springboard {
private static var springboardApp = XCUIApplication(bundleIdentifier: "com.apple.springboard")
class func deleteApp(name: String) {
XCUIApplication().terminate()
springboardApp.activate()
sleep(1)
let appIcon = springboardApp.icons.matching(identifier: name).firstMatch
appIcon.press(forDuration: 1.3)
sleep(1)
springboardApp.buttons["Delete App"].tap()
let deleteButton = springboardApp.alerts.buttons["Delete"].firstMatch
if deleteButton.waitForExistence(timeout: 5) {
deleteButton.tap()
}
}
}
You can ask your app to "clean up" itself
You use XCUIApplication.launchArguments to set some flag
In AppDelegate you check
if NSProcessInfo.processInfo().arguments.contains("YOUR_FLAG_NAME_HERE") {
// Do a clean up here
}
I used the #ODM answer, but modified it to work for Swift 4. NB: some S/O answers don't differentiate the Swift versions, which sometimes have fairly fundamental differences. I've tested this on an iPhone 7 simulator and an iPad Air simulator in portrait orientation, and it worked for my app.
Swift 4
import XCTest
import Foundation
class Springboard {
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let settings = XCUIApplication(bundleIdentifier: "com.apple.Preferences")
/**
Terminate and delete the app via springboard
*/
func deleteMyApp() {
XCUIApplication().terminate()
// Resolve the query for the springboard rather than launching it
springboard.activate()
// Rotate back to Portrait, just to ensure repeatability here
XCUIDevice.shared.orientation = UIDeviceOrientation.portrait
// Sleep to let the device finish its rotation animation, if it needed rotating
sleep(2)
// Force delete the app from the springboard
// Handle iOS 11 iPad 'duplication' of icons (one nested under "Home screen icons" and the other nested under "Multitasking Dock"
let icon = springboard.otherElements["Home screen icons"].scrollViews.otherElements.icons["YourAppName"]
if icon.exists {
let iconFrame = icon.frame
let springboardFrame = springboard.frame
icon.press(forDuration: 2.5)
// Tap the little "X" button at approximately where it is. The X is not exposed directly
springboard.coordinate(withNormalizedOffset: CGVector(dx: ((iconFrame.minX + 3) / springboardFrame.maxX), dy:((iconFrame.minY + 3) / springboardFrame.maxY))).tap()
// Wait some time for the animation end
Thread.sleep(forTimeInterval: 0.5)
//springboard.alerts.buttons["Delete"].firstMatch.tap()
springboard.buttons["Delete"].firstMatch.tap()
// Press home once make the icons stop wiggling
XCUIDevice.shared.press(.home)
// Press home again to go to the first page of the springboard
XCUIDevice.shared.press(.home)
// Wait some time for the animation end
Thread.sleep(forTimeInterval: 0.5)
// Handle iOS 11 iPad 'duplication' of icons (one nested under "Home screen icons" and the other nested under "Multitasking Dock"
let settingsIcon = springboard.otherElements["Home screen icons"].scrollViews.otherElements.icons["Settings"]
if settingsIcon.exists {
settingsIcon.tap()
settings.tables.staticTexts["General"].tap()
settings.tables.staticTexts["Reset"].tap()
settings.tables.staticTexts["Reset Location & Privacy"].tap()
// Handle iOS 11 iPad difference in error button text
if UIDevice.current.userInterfaceIdiom == .pad {
settings.buttons["Reset"].tap()
}
else {
settings.buttons["Reset Warnings"].tap()
}
settings.terminate()
}
}
}
}
I used the #Chase Holland answer and updated the Springboard class following the same approach to reset the content and settings using the Settings app. This is useful when you need to reset permissions dialogs.
import XCTest
class Springboard {
static let springboard = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.springboard")
static let settings = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.Preferences")
/**
Terminate and delete the app via springboard
*/
class func deleteMyApp() {
XCUIApplication().terminate()
// Resolve the query for the springboard rather than launching it
springboard.resolve()
// Force delete the app from the springboard
let icon = springboard.icons["MyAppName"]
if icon.exists {
let iconFrame = icon.frame
let springboardFrame = springboard.frame
icon.pressForDuration(1.3)
// Tap the little "X" button at approximately where it is. The X is not exposed directly
springboard.coordinateWithNormalizedOffset(CGVectorMake((iconFrame.minX + 3) / springboardFrame.maxX, (iconFrame.minY + 3) / springboardFrame.maxY)).tap()
springboard.alerts.buttons["Delete"].tap()
// Press home once make the icons stop wiggling
XCUIDevice.sharedDevice().pressButton(.Home)
// Press home again to go to the first page of the springboard
XCUIDevice.sharedDevice().pressButton(.Home)
// Wait some time for the animation end
NSThread.sleepForTimeInterval(0.5)
let settingsIcon = springboard.icons["Settings"]
if settingsIcon.exists {
settingsIcon.tap()
settings.tables.staticTexts["General"].tap()
settings.tables.staticTexts["Reset"].tap()
settings.tables.staticTexts["Reset Location & Privacy"].tap()
settings.buttons["Reset Warnings"].tap()
settings.terminate()
}
}
}
}
I see a lot of answers to uninstall your app in setUp or tearDown of your test.
But you can easily uninstall your app before launching your tests by adding a run script phase in your test target.
To do so :
Select your application's Xcode project
Select your test target
Select "Build Phases"
Tap on "+" and "New Run Script Phase"
Then, replace the placeholder # Type a script or drag a script file from your workspace to insert its path. by the command :
xcrun simctl boot ${TARGET_DEVICE_IDENTIFIER}
xcrun simctl uninstall ${TARGET_DEVICE_IDENTIFIER} YOUR_APP_BUNDLE
Starting Xcode 11.4, if all you want is to reset permissions, you can use resetAuthorizationStatus(for:) on instance of XCUIApplication, see
https://developer.apple.com/documentation/xctest/xcuiapplication/3526066-resetauthorizationstatusforresou
You can also use simctl if needed, quoted from Xcode 11.4 Release Notes:
simctl now supports modifying privacy permissions. You can modify privacy permissions to create known states for testing purposes. For example, to allow an example app to access the photo library without any prompts:
xcrun simctl privacy <device> grant photos com.example.app
To reset all permissions to defaults, as if the app had never been installed before:
xcrun simctl privacy <device> reset all com.example.app.
Working solution for iOS14
final class Springboard {
private static var springboardApp = XCUIApplication(bundleIdentifier: "com.apple.springboard")
class func deleteApp(name: String) {
XCUIApplication().terminate()
springboardApp.activate()
sleep(1)
let appIcon = springboardApp.icons.matching(identifier: name).firstMatch
appIcon.press(forDuration: 1.3)
sleep(1)
springboardApp.buttons["Remove App"].tap()
let deleteButton = springboardApp.alerts.buttons["Delete App"].firstMatch
if deleteButton.waitForExistence(timeout: 5) {
deleteButton.tap()
springboardApp.alerts.buttons["Delete"].tap()
}
}
}
For iOS 11 sims an up, I made an ever so slight modification to tap the "x" icon and where we tap per the fix #Code Monkey suggested. Fix works well on both 10.3 and 11.2 phone sims. For the record, I'm using swift 3. Thought i'd through some code out there to copy and paste to find the fix a little easier. :)
import XCTest
class Springboard {
static let springboard = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.springboard")
class func deleteMyApp() {
XCUIApplication().terminate()
// Resolve the query for the springboard rather than launching it
springboard!.resolve()
// Force delete the app from the springboard
let icon = springboard!.icons["My Test App"]
if icon.exists {
let iconFrame = icon.frame
let springboardFrame = springboard!.frame
icon.press(forDuration: 1.3)
springboard!.coordinate(withNormalizedOffset: CGVector(dx: (iconFrame.minX + 3 * UIScreen.main.scale) / springboardFrame.maxX, dy: (iconFrame.minY + 3 * UIScreen.main.scale) / springboardFrame.maxY)).tap()
springboard!.alerts.buttons["Delete"].tap()
}
}
}
There are so many variations of the answer, not even sure if I should add on to that, but in case someone need a universal solution:
iOS 14.6 and 15 beta
class func deleteApp() {
XCUIApplication().terminate()
// Force delete the app from the springboard
let icon = springboard.icons["APP_NAME"]
if icon.exists {
icon.press(forDuration: 1.3)
springboard.buttons["Remove App"].tap()
springboard.alerts.buttons["Delete App"].tap()
springboard.alerts.buttons["Delete"].tap()
// Press home once to make the icons stop wiggling
XCUIDevice.shared.press(.home)
}
}
This seems to work for me on iOS 12.1 & simulator
class func deleteApp(appName: String) {
XCUIApplication().terminate()
// Force delete the app from the springboard
let icon = springboard.icons[appName]
if icon.exists {
icon.press(forDuration: 2.0)
icon.buttons["DeleteButton"].tap()
sleep(2)
springboard.alerts["Delete “\(appName)”?"].buttons["Delete"].tap()
sleep(2)
XCUIDevice.shared.press(.home)
}
}
iOS 13.1/Swift 5.1 UI based deletion
static let springboard = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.springboard")!
class func deleteApp() {
XCUIApplication().terminate()
XCUIDevice.shared.press(.home)
XCUIDevice.shared.press(.home)
let icon = springboard.icons["YourApplication"]
if !icon.exists { return }
springboard.swipeLeft()
springboard.activate()
Thread.sleep(forTimeInterval: 1.0)
icon.press(forDuration: 1.3)
springboard.buttons["Rearrange Apps"].eventuallyExists().tap()
icon.buttons["DeleteButton"].eventuallyExists().tap()
springboard.alerts.buttons["Delete"].eventuallyExists().tap()
XCUIDevice.shared.press(.home)
XCUIDevice.shared.press(.home)
}
Updating Craig Fishers answer for Swift 4. Updated for iPad in landscape, probably only works for landscape left.
import XCTest
class Springboard {
static let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
class func deleteMyApp(name: String) {
// Force delete the app from the springboard
let icon = springboard.icons[name]
if icon.exists {
let iconFrame = icon.frame
let springboardFrame = springboard.frame
icon.press(forDuration: 2.0)
var portaitOffset = 0.0 as CGFloat
if XCUIDevice.shared.orientation != .portrait {
portaitOffset = iconFrame.size.width - 2 * 3 * UIScreen.main.scale
}
let coord = springboard.coordinate(withNormalizedOffset: CGVector(dx: (iconFrame.minX + portaitOffset + 3 * UIScreen.main.scale) / springboardFrame.maxX, dy: (iconFrame.minY + 3 * UIScreen.main.scale) / springboardFrame.maxY))
coord.tap()
let _ = springboard.alerts.buttons["Delete"].waitForExistence(timeout: 5)
springboard.alerts.buttons["Delete"].tap()
XCUIDevice.shared.press(.home)
}
}
}
Here is an Objective C version of the above answers to delete an App and reset warnings (tested on iOS 11 & 12):
- (void)uninstallAppNamed:(NSString *)appName {
[[[XCUIApplication alloc] init] terminate];
XCUIApplication *springboard = [[XCUIApplication alloc] initWithBundleIdentifier:#"com.apple.springboard"];
[springboard activate];
XCUIElement *icon = springboard.otherElements[#"Home screen icons"].scrollViews.otherElements.icons[appName];
if (icon.exists) {
[icon pressForDuration:2.3];
[icon.buttons[#"DeleteButton"] tap];
sleep(2);
[[springboard.alerts firstMatch].buttons[#"Delete"] tap];
sleep(2);
[[XCUIDevice sharedDevice] pressButton:XCUIDeviceButtonHome];
sleep(2);
}
}
..
- (void)resetWarnings {
XCUIApplication *settings = [[XCUIApplication alloc] initWithBundleIdentifier:#"com.apple.Preferences"];
[settings activate];
sleep(2);
[settings.tables.staticTexts[#"General"] tap];
[settings.tables.staticTexts[#"Reset"] tap];
[settings.tables.staticTexts[#"Reset Location & Privacy"] tap];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[settings.buttons[#"Reset"] tap];
} else {
[settings.buttons[#"Reset Warnings"] tap];
}
sleep(2);
[settings terminate];
}
This works for me in all OS version(iOS11,12 & 13)
static let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
func deleteApp() {
XCUIApplication().terminate()
springboard.activate()
let icon = springboard.icons[appName]
if icon.exists {
icon.firstMatch.press(forDuration: 5)
icon.buttons["DeleteButton"].tap()
let deleteConfirmation = springboard.alerts["Delete “\(appName)”?"].buttons["Delete"]
XCTAssertTrue(deleteConfirmation.waitForExistence(timeout: 5), "Delete confirmation not shown")
deleteConfirmation.tap()
}
}
After some experiments, I've ended implementing a clearer solution that covers different iOS versions:
import XCTest
private enum Constants {
static let springboardBundleIdentifier = "com.apple.springboard"
static let appIconName = "Your App Name"
static let appIconPressShortDuration: TimeInterval = 2.0
static let appIconPressLongDuration: TimeInterval = 3.0
static let deleteAppButton = "Delete App"
static let removeAppButton = "Remove App"
static let deleteButton = "Delete"
static let deleteButtonVectorOffset: CGFloat = 3.0
}
final class SpringboardManager {
private static let springboard = XCUIApplication(bundleIdentifier: Constants.springboardBundleIdentifier)
static func deleteApp(_ app: XCUIApplication) {
if app.exists && app.isHittable {
XCUIDevice.shared.press(.home)
}
app.terminate()
self.deleteAppIfNeeded(with: Constants.appIconName)
sleep(1)
}
private static func deleteAppIfNeeded(with iconName: String) {
let appIcon = self.springboard.icons[iconName]
guard appIcon.exists else {
return
}
appIcon.press(forDuration: Constants.appIconPressShortDuration)
if let deleteListButton = self.deleteListButton() {
deleteListButton.tap()
self.pressDeleteAlertButtons()
} else {
appIcon.press(forDuration: Constants.appIconPressLongDuration)
self.pressDeleteTopLeftButton(for: appIcon)
self.pressDeleteAlertButtons()
}
}
}
private extension SpringboardManager {
static func pressDeleteAlertButtons() {
self.pressDeleteAlertButton(self.deleteAppAlertButton())
self.pressDeleteAlertButton(self.deleteAlertButton())
}
static func pressDeleteAlertButton(_ button: XCUIElement?) {
guard let button = button else {
return
}
button.tap()
}
static func pressDeleteTopLeftButton(for appIcon: XCUIElement) {
let iconFrame = appIcon.frame
let springboardFrame = self.springboard.frame
let deleteButtonVector = CGVector(
dx: (iconFrame.minX + Constants.deleteButtonVectorOffset) / springboardFrame.maxX,
dy: (iconFrame.minY + Constants.deleteButtonVectorOffset) / springboardFrame.maxY)
let deleteButtonCoordinate = self.springboard.coordinate(withNormalizedOffset: deleteButtonVector)
deleteButtonCoordinate.tap()
}
}
private extension SpringboardManager {
static func deleteListButton() -> XCUIElement? {
sleep(1)
let removeListButton = self.springboard.buttons[Constants.removeAppButton]
let deleteListButton = self.springboard.buttons[Constants.deleteAppButton]
if removeListButton.exists {
return removeListButton
} else if deleteListButton.exists {
return deleteListButton
}
return nil
}
static func deleteAppAlertButton() -> XCUIElement? {
sleep(1)
let deleteAppButton = self.springboard.alerts.buttons[Constants.deleteAppButton]
if deleteAppButton.exists {
return deleteAppButton
}
return nil
}
static func deleteAlertButton() -> XCUIElement? {
sleep(1)
let deleteButton = self.springboard.alerts.buttons[Constants.deleteButton]
if deleteButton.exists {
return deleteButton
}
return nil
}
}