The iOS web debugger in Safari is the bee's knees, but it closes every time the Simulator is restarted. Not only is it annoying to re-open it from the menu after every build, but it makes it tricky to debug any behavior that happens during startup.
Is there a way to set up a trigger in Xcode to automatically open the Safari debugger after every build, or perhaps a way to build a shell script or Automator action to do a build and immediately open the debugger?
This is a partial solution. This opens the debug window of Safari with one click which is a lot better but not automatic.
Open Script Editor on your mac (Command + Space Bar and type in Script Editor)
Paste in this code:
-- `menu_click`, by Jacob Rus, September 2006
--
-- Accepts a list of form: `{"Finder", "View", "Arrange By", "Date"}`
-- Execute the specified menu item. In this case, assuming the Finder
-- is the active application, arranging the frontmost folder by date.
on menu_click(mList)
local appName, topMenu, r
-- Validate our input
if mList's length < 3 then error "Menu list is not long enough"
-- Set these variables for clarity and brevity later on
set {appName, topMenu} to (items 1 through 2 of mList)
set r to (items 3 through (mList's length) of mList)
-- This overly-long line calls the menu_recurse function with
-- two arguments: r, and a reference to the top-level menu
tell application "System Events" to my menu_click_recurse(r, ((process appName)'s ¬
(menu bar 1)'s (menu bar item topMenu)'s (menu topMenu)))
end menu_click
on menu_click_recurse(mList, parentObject)
local f, r
-- `f` = first item, `r` = rest of items
set f to item 1 of mList
if mList's length > 1 then set r to (items 2 through (mList's length) of mList)
-- either actually click the menu item, or recurse again
tell application "System Events"
if mList's length is 1 then
click parentObject's menu item f
else
my menu_click_recurse(r, (parentObject's (menu item f)'s (menu f)))
end if
end tell
end menu_click_recurse
menu_click({"Safari", "Develop", "Simulator", "index.html"})
Once the simulator has opened, click run on your script (you might need to allow the script editor in the settings the first time).
(Optional) You can save your the scripts as an app so that you don't have to have the script editor open.
There is question that should be marked a duplicate that describes using setTimeout() to give you enough time to switch windows over to Safari and set a breakpoint.
Something like this, where startMyApp is the bootstrap function of your app:
setTimeout(function () {
startMyApp();
}, 20000);
It is super ghetto, but does work. I've submitted a feature request via http://www.apple.com/feedback/safari.html too to close the loop.
Extending upon the #Prisoner's answer, if you use WKWebView you could:
let contentController:WKUserContentController = WKUserContentController()
let pauseForDebugScript = WKUserScript(source: "window.alert(\"Go, turn on Inspector, I'll hold them back!\")",
injectionTime: WKUserScriptInjectionTime.AtDocumentStart,
forMainFrameOnly: true)
contentController.addUserScript(pauseForDebugScript)
let config = WKWebViewConfiguration()
config.userContentController = contentController
//Init browser with configuration (our injected script)
browser = WKWebView(frame: CGRect(x:0, y:0, width: view.frame.width, height: containerView.frame.height), configuration:config)
also important thing is to implement alert handler from WKUIDelegate protocol
//MARK: WKUIDelegate
func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String,
initiatedByFrame frame: WKFrameInfo, completionHandler: () -> Void) {
let alertController = UIAlertController(title: message, message: nil,
preferredStyle: UIAlertControllerStyle.Alert);
alertController.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel) {
_ in completionHandler()}
);
self.presentViewController(alertController, animated: true, completion: {});
}
and one little thing just in case you could have an UIAlertController:supportedInterfaceOrientations was invoked recursively error add following extension (From this SO Answer)
extension UIAlertController {
public override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.Portrait
}
public override func shouldAutorotate() -> Bool {
return false
}
}
Combining Tom's answer with my solution, first do the following:
Create an application as Tom describes above
In "System Preferences -> Security & Privacy -> Privacy -> Accessibility" add your new Script Applicaiton and make sure it is allowed to control your computer
Now, I'm using cordova and from the command line the following builds, runs emulator and opens safari debug console:
cordova build ios; cordova emulate ios; open /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app; sleep 1 ; open /PATH/TO/OpenDevelop.app/
Make sure to replace /PATH/TO/ with the appropriate path to where you saved your script.
Humm,I don't think you can do that,but you can just "CTRL+R" in the web debugger.
Related
I am writing a Focus system extension (sandboxed, Cocoa, entitlements set) for macOS 13/Ventura using Xcode 14.2
I have the extension loading it's UI into the macOS system settings > Focus pane.
so here are the issues:
Even though it is loaded, it doesn't seem to ever run the perform() function when the UI is changed by the user or the user invokes Focus > Do Not Disturb.
What can be done in the perform() function? Like, what is supposed to go there? Nothing seems to work.
import AppIntents
struct MacOSFocus: SetFocusFilterIntent {
static var title: LocalizedStringResource {
return "Focus Settings"
}
// The description as it appears in the Settings app
static var description: LocalizedStringResource? = "Focus Settings" // name under Minus icon in options list
// How a configured filter appears on the Focus details screen
var displayRepresentation: DisplayRepresentation {
return DisplayRepresentation(stringLiteral: "Focus Settings") // name under filter once added to Foucs
}
#Parameter(title: "Show Task Bar", default: false)
var showDefaultTaskBar: Bool
#Parameter(title: "Start Timer")
var startTimer: Bool
func perform() async throws -> some IntentResult {
// This doesnt seem to run
// What can I put here?
// I need to write string data to a text file somewhere or communicate with the host app in some way.
return .result()
}
}
Just trying to get unstuck. Thanks for any help.
Tried adding an NSLog() call in the perform() function for debugging. Even tried using NSSound.beep() just to check that it is getting called. Didn't work.
My iOS application is registered to handle certain file types and I want to include coverage for this in my automated tests.
Is there any way to simulate a file opening (e.g. after receiving a shared file or opening an attachment from an e-mail) in an XCUI test?
My "Plan B" is to host the files somewhere and then try and "open" them (in my tests) via Safari...
I went with Plan B. Here's my code...
func testForStackOverflow() {
let myApplication = XCUIApplication()
myApplication.launch()
_ = myApplication.wait(for: .runningForeground, timeout: 10)
let safari = XCUIApplication(bundleIdentifier: "com.apple.mobilesafari")
safari.launch()
_ = safari.wait(for: .runningForeground, timeout: 10)
let fixturesUrl = "https://server.com/fixtures.zip"
// This works with the "weird" address bar at the bottom in iOS 15.2
safari.textFields["TabBarItemTitle"].tap()
safari.textFields["URL"].typeText(fixturesUrl)
safari.buttons["Go"].tap()
safari.buttons["Download"].tap()
let files = XCUIApplication(bundleIdentifier: "com.apple.DocumentsApp")
files.launch()
_ = files.wait(for: .runningForeground, timeout: 10)
// This will only work in "List View" (not "Icon View")
files.staticTexts["Downloads"].tap()
files.staticTexts["fixtures.zip"].tap()
files.staticTexts["fixtures"].tap()
files.staticTexts["file.ext"].tap()
myApplication.buttons["Done"].tap()
// Cleanup
files.activate()
files.buttons["Downloads"].tap()
files.staticTexts["fixtures"].press(forDuration: 2)
files.buttons["Delete"].tap()
files.staticTexts["fixtures.zip"].press(forDuration: 2)
files.buttons["Delete"].tap()
files.buttons["On My iPhone"].tap()
}
I want to test my macOS application. It uses your Macbook's camera, and want to handle this in my UITest. However I cannot get it working. Here is my NOT working code. This code triggers to notification, and I'm presented an alert to allow access to my camera, but the closure is not getting called. Thanks fo any help.
There are many solutions for iOS, but I need it on macOS.
let alertHandler = addUIInterruptionMonitor(withDescription: "Camera Permission Alert") { (alert) -> Bool in
if alert.buttons.matching(identifier: "OK").count > 0 {
alert.buttons["OK"].click()
self.app.click()
return true
} else {
return false
}
}
XCTAssertTrue(startButton.waitForExistence(timeout: 1.0))
startButton.click()
XCTAssertTrue(recordButton.waitForExistence(timeout: 20.0))
recordButton.click()
wait(for: 8)
recordButton.click()
removeUIInterruptionMonitor(alertHandler)
}
I managed to make interruption monitor work on macOS by adding an extra interaction after the interaction that triggers the system dialog (be it camera access or else). So in your example I would add an action after startButton.click() (if that is what triggers the camera access dialog).
Example:
func testCamera() {
let alertHandler = addUIInterruptionMonitor(withDescription: "Camera Permission Alert") { (alert) -> Bool in
if alert.buttons.matching(identifier: "OK").count > 0 {
alert.buttons["OK"].click()
self.app.click()
return true
} else {
return false
}
}
useCameraButton.click()
// try to interact with the app by clicking on the app's window
app.windows.first().click()
// at this point the handler should intercept the system interruption
// and blocks further execution until handler does return
// try to use the camera again
useCameraButton.click()
removeUIInterruptionMonitor(alertHandler)
}
Hint about this behaviour in Apple's documentation:
When an alert or other modal UI is an expected part of the
test workflow, don't write a UI interruption monitor. The test won’t
use the monitor because the modal UI isn’t blocking the test. A UI
test only tries its UI interruption monitors if the elements it needs
to interact with to complete the test are blocked by an interruption
from an unrelated UI.
https://developer.apple.com/documentation/xctest/xctestcase/handling_ui_interruptions
I'm adding some functionality to a menubar app. I want to execute a few lines of code that copies some text to the clipboard when a combination of keys are pressed (e.g. cmd + alt + L). This should work globally, i.e any time these keys are pressed.
Not sure how to go about doing this, I tried overriding the keyDown method but it gives an error in AppDelegate.swift saying that there's no method to override.
First step you need to add a global monitor.
NSEvent.addGlobalMonitorForEvents(matching: .keyDown, handler: {
self.keyDown(with: $0)
})
But it can be also your func.
Second step is to handle these three keys
Read flags from NSApp.currentEvent?.modifierFlags and check if they contain .option and .command flags
Example
guard let flags = NSApp.currentEvent?.modifierFlags else {
return
}
let optionKeyIsPressed = flags.contains(.option)
At last key you can read from NSEvent property keyCode.
The keyCode of later "L" you can read from kVK_ANSI_L
Hope it's all that you need to solve your problem, good luck.
I am trying to implement Speech-to-text feature for watchkit app.
I referred this question which has sample code.
Following is the code I tried:
self.presentTextInputControllerWithSuggestions(["Start it", "Stop it"], allowedInputMode: .Plain, completion: { (selectedAnswers) -> Void in
if selectedAnswers.count > 0 {
if let spokenReply = selectedAnswers[0] as? String {
self.label.setText("\(spokenReply)")
}
}
})
label is a label to display text I speak.
When I run it, it shows the screen where you are supposed to speak (Siri kind of screen) and you have two options on top: ‘Cancel', and ‘Done'. Once I am done speaking, I tap on ‘Done’ but screen doesn’t go away or shows me initial screen, I always have to tap on ‘Cancel’ to go back, and I don’t get any speech data in form of text. I checked it and seems like selectedAnswers is always an empty array, unless I tap on the "Start it"/"Stop it" options.
Can anyone help me with this? I want to show the spoken message on label. I have code inside awakeWithContext method in InterfaceController.swift file, am I supposed to put it somewhere else?
I am using iPhone with iOS 9 beta 2 and watchOS 2 beta on AppleWatch.
Thanks!
You can ask for user input and give him suggestion (see Swift example bellow).
self.presentTextInputControllerWithSuggestions(["suggestion 1", "suggestion 2"] allowedInputMode: .Plain, completion: { (answers) -> Void in
if answers && answers.count > 0 {
if let answer = answers[0] as? String {
println("\answer")
}
}
})
If suggestion is nil it goes directly to dictation. It is not working on the simulator but it is on real watch.
Your approach is correct but something is wrong with your SIRI , try changing the language.
It should work like these.