├── screenshot.png ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── Package.swift ├── LICENSE ├── CHANGELOG.md ├── Sources └── main.swift └── README.md /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessesquires/Nine41/HEAD/screenshot.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # ruby tools 5 | .bundle/ 6 | vendor/ 7 | 8 | # docs 9 | docs/docsets/ 10 | 11 | # Xcode 12 | xcuserdata/ 13 | 14 | ## Obj-C/Swift specific 15 | *.hmap 16 | 17 | ## App packaging 18 | *.ipa 19 | *.dSYM.zip 20 | *.dSYM 21 | 22 | ## Playgrounds 23 | timeline.xctimeline 24 | playground.xcworkspace 25 | 26 | # Swift Package Manager 27 | .swiftpm 28 | .build/ 29 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Documentation for all configuration options: 2 | # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "bundler" 7 | directory: "/" 8 | schedule: 9 | interval: "weekly" 10 | labels: [] 11 | 12 | - package-ecosystem: "github-actions" 13 | directory: "/" 14 | schedule: 15 | interval: "weekly" 16 | labels: [] 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions Virtual Environments 2 | # https://github.com/actions/virtual-environments/ 3 | 4 | name: CI 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | branches: 12 | - main 13 | 14 | env: 15 | DEVELOPER_DIR: /Applications/Xcode_16.2.app/Contents/Developer 16 | 17 | jobs: 18 | main: 19 | name: Build and Run 20 | runs-on: macos-15 21 | steps: 22 | - name: git checkout 23 | uses: actions/checkout@v6 24 | 25 | - name: xcode version 26 | run: xcodebuild -version -sdk 27 | 28 | - name: swift build 29 | run: swift build 30 | 31 | - name: swift run 32 | run: swift run 33 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:6.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | // GitHub 5 | // https://github.com/jessesquires/Nine41 6 | // 7 | // Copyright © 2019 Jesse Squires 8 | // https://www.jessesquires.com 9 | // 10 | 11 | import PackageDescription 12 | 13 | let package = Package( 14 | name: "Nine41", 15 | platforms: [ 16 | .macOS(.v11) 17 | ], 18 | products: [ 19 | .executable(name: "nine41", targets: ["Nine41"]) 20 | ], 21 | targets: [ 22 | .executableTarget( 23 | name: "Nine41", 24 | path: "Sources" 25 | ) 26 | ], 27 | swiftLanguageModes: [.v6] 28 | ) 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jesse Squires 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | The changelog for `Nine41`. Also see the [releases](https://github.com/jessesquires/Nine41/releases) on GitHub. 4 | 5 | 4.0.2 6 | ----- 7 | 8 | - Fix Daylight Savings bug (but good this time) ([#73](https://github.com/jessesquires/Nine41/issues/73), [@joeljfischer](https://github.com/joeljfischer)) 9 | 10 | 4.0.1 11 | ----- 12 | 13 | - Fix Daylight Savings bug (again, I think, lol) ([#72](https://github.com/jessesquires/Nine41/issues/72), [@jessesquires](https://github.com/jessesquires)) 14 | 15 | 4.0.0 16 | ----- 17 | 18 | - Upgraded to Xcode 16 and Swift 6. 19 | - Dropped support for CocoaPods. 20 | 21 | 3.0.0 22 | ----- 23 | 24 | - Minimum macOS 11.0 now required 25 | - Fixed "Invalid argument" error for `--time` value in Xcode 15.3 ([#70](https://github.com/jessesquires/Nine41/issues/70), [@jessesquires](https://github.com/jessesquires)) 26 | - Performance optimizations, only create date formatters once ([@jessesquires](https://github.com/jessesquires)) 27 | 28 | 2.3.1 29 | ----- 30 | 31 | - Fix Daylight Savings bug ([@jessesquires](https://github.com/jessesquires)) 32 | - Xcode 14 and Swift 5.7 ([@jessesquires](https://github.com/jessesquires)) 33 | 34 | 2.3.0 35 | ----- 36 | 37 | - Xcode 13 and Swift 5.5 ([@jessesquires](https://github.com/jessesquires)) 38 | 39 | 2.2.0 40 | ----- 41 | 42 | - Update to Swift 5.3 ([@jessesquires](https://github.com/jessesquires)) 43 | 44 | 2.1.0 45 | ----- 46 | 47 | - Fixed issue where script might hang. ([#11](https://github.com/jessesquires/Nine41/issues/11), [@jessesquires](https://github.com/jessesquires)) 48 | 49 | - Fixed issue where "9:41" would display incorrect date/time in timezones outside of US Pacific. ([#13](https://github.com/jessesquires/Nine41/issues/13), [@aoenth](https://github.com/aoenth)) 50 | 51 | - Added support for CocoaPods. See `README` for instructions. ([#12](https://github.com/jessesquires/Nine41/issues/12), [@ricsantos](https://github.com/ricsantos)) 52 | 53 | 2.0.1 54 | ----- 55 | 56 | - Added an `.executable` product to the Swift package ([@jessesquires](https://github.com/jessesquires)) 57 | 58 | 2.0.0 59 | ----- 60 | 61 | - Now requires Xcode 11.4 62 | 63 | - Add Swift Package Manager support ([@jessesquires](https://github.com/jessesquires)) 64 | 65 | - Set date text to `Tuesday January 9`, which shows on iPad status bars ([#4](https://github.com/jessesquires/Nine41/pull/4), [@tfe](https://github.com/tfe)) 66 | 67 | - Setup SwiftLint for project ([#5](https://github.com/jessesquires/Nine41/issues/5), [@jessesquires](https://github.com/jessesquires)) 68 | 69 | - Setup Danger for project ([#9](https://github.com/jessesquires/Nine41/issues/9), [@jessesquires](https://github.com/jessesquires)) 70 | 71 | - Override the operator (carrier) name to be the empty string ([#8](https://github.com/jessesquires/Nine41/issues/8), [@jessesquires](https://github.com/jessesquires)) 72 | 73 | 1.0 74 | --- 75 | 76 | Initial release. 77 | -------------------------------------------------------------------------------- /Sources/main.swift: -------------------------------------------------------------------------------- 1 | #!/usr/bin/swift 2 | 3 | // 4 | // GitHub 5 | // https://github.com/jessesquires/Nine41 6 | // 7 | // Copyright © 2019 Jesse Squires 8 | // https://www.jessesquires.com 9 | // 10 | 11 | import Foundation 12 | 13 | #if os(OSX) 14 | 15 | let dateFormatter = DateFormatter() 16 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" 17 | dateFormatter.timeZone = TimeZone.current 18 | dateFormatter.locale = Locale.current 19 | 20 | let isoDateFormatter = ISO8601DateFormatter() 21 | isoDateFormatter.formatOptions = [ 22 | .withFullDate, 23 | .withDashSeparatorInDate, 24 | .withFullTime, 25 | .withColonSeparatorInTime, 26 | .withFractionalSeconds, 27 | .withTimeZone 28 | ] 29 | 30 | /// An ISO date/time string for 9:41 AM on Tuesday January 9, 2007 31 | let date = dateFormatter.date(from: "2007-01-09T09:41:00")! 32 | let simulatorDateTimeText = isoDateFormatter.string(from: date) 33 | 34 | extension Process { 35 | /// Creates a process to execute `xcrun`. 36 | /// 37 | /// - Parameter args: The arguments to pass to `xcrun`. 38 | @discardableResult 39 | func xcrun(_ args: String...) -> Data { 40 | self.launchPath = "/usr/bin/xcrun" 41 | self.arguments = args 42 | 43 | let pipe = Pipe() 44 | self.standardOutput = pipe 45 | 46 | self.launch() 47 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 48 | self.waitUntilExit() 49 | return data 50 | } 51 | 52 | /// Executes `xcrun simctl list devices` 53 | @discardableResult 54 | func xcrun_list_devices() -> Data { 55 | self.xcrun("simctl", "list", "devices", "-j") 56 | } 57 | 58 | /// Executes `xcrun simctl status_bar` on the specified device to set overrides. 59 | /// 60 | /// - Parameter device: The device for which status bar values should be overridden. 61 | func xcrun_fix_status_bar(_ device: String) { 62 | self.xcrun( 63 | "simctl", "status_bar", device, "override", 64 | "--time", "\(simulatorDateTimeText)", 65 | "--dataNetwork", "wifi", 66 | "--wifiMode", "active", 67 | "--wifiBars", "3", 68 | "--cellularMode", "active", 69 | "--cellularBars", "4", 70 | "--operatorName", "", 71 | "--batteryState", "charged", 72 | "--batteryLevel", "100" 73 | ) 74 | } 75 | 76 | /// Executes `xcrun simctl status_bar` on the specified device to clear all overrides. 77 | func xcrun_clear_status_bar(_ device: String) { 78 | self.xcrun("simctl", "status_bar", device, "clear") 79 | } 80 | } 81 | 82 | print("Fixing status bars...") 83 | 84 | let deviceData = Process().xcrun_list_devices() 85 | let json = (try? JSONSerialization.jsonObject(with: deviceData, options: [])) as! [String: Any] 86 | let runtimes = json["devices"] as! [String: [Any]] 87 | let allDevices = runtimes.values.flatMap { $0 } as! [[String: AnyHashable]] 88 | 89 | var fixed = false 90 | 91 | allDevices.forEach { 92 | let available = $0["isAvailable"] as! Bool 93 | let name = $0["name"] as! String 94 | let state = $0["state"] as! String 95 | let udid = $0["udid"] as! String 96 | 97 | if available && state != "Shutdown" { 98 | Process().xcrun_fix_status_bar(udid) 99 | print("✅ \(name), \(udid)") 100 | fixed = true 101 | } 102 | } 103 | 104 | if !fixed { 105 | print("❌ No simulators are running. Launch the iOS simulator first.") 106 | } 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nine41 ![CI](https://github.com/jessesquires/Nine41/workflows/CI/badge.svg) 2 | 3 | *Automate overriding the status bars for all running iOS simulators* 4 | 5 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fjessesquires%2FNine41%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/jessesquires/Nine41)
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fjessesquires%2FNine41%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/jessesquires/Nine41) 6 | 7 | ![perfect status bar](https://raw.githubusercontent.com/jessesquires/Nine41/main/screenshot.png) 8 | 9 | ## About 10 | 11 | Blog posts: 12 | 13 | * [Fully automating perfect status bar overrides for iOS simulators with Nine41](https://www.jessesquires.com/blog/2020/04/13/fully-automating-perfect-status-bar-overrides-for-ios-simulators/) 14 | * [A script to automate overriding iOS simulator status bar values](https://www.jessesquires.com/blog/2019/09/30/automating-simctl-status-bar/) 15 | * [Overriding status bar display settings in the iOS simulator](https://www.jessesquires.com/blog/2019/09/26/overriding-status-bar-settings-ios-simulator/) 16 | 17 | [Xcode 11](https://developer.apple.com/documentation/xcode_release_notes/xcode_11_release_notes) shipped with `simctl status_bar`, a tool to override the status bar values in the simulator so you can take perfect screenshots. 18 | 19 | However, it has some issues: 20 | * The overrides do not persist across launches of the simulator 21 | * The numerous override options are difficult to remember 22 | * There are no sensible defaults 23 | 24 | This script fixes most of those issues. It overrides the status bars for all currently running simulators using "Apple's defaults" — full cellular bars, full wifi bars, full battery, no "carrier" name, and `9:41` for the time. 25 | 26 | ## Requirements 27 | 28 | - Swift 6.0+ 29 | - Xcode 16.0+ 30 | 31 | ## Installation 32 | 33 | ### [Swift Package Manager](https://swift.org/package-manager/) 34 | 35 | Add `Nine41` to the `dependencies` value of your `Package.swift`. 36 | 37 | ```swift 38 | dependencies: [ 39 | .package(url: "https://github.com/jessesquires/Nine41.git", from: "4.0.0") 40 | ] 41 | ``` 42 | 43 | Alternatively, you can add the package [directly via Xcode](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app). 44 | 45 | ## Usage 46 | 47 | After cloning the repo, you can create a custom bash command: 48 | 49 | ```bash 50 | function nine41() { 51 | swift run --package-path /PATH/TO/Nine41/ 52 | } 53 | ``` 54 | 55 | Then you can run this from the command line. 56 | 57 | Example run with 2 open simulators: 58 | 59 | ```bash 60 | $ nine41 61 | Fixing status bars... 62 | ✅ iPhone 15, 65A6C323-E74D-452C-B85E-7F576259E022 63 | ✅ iPhone 16 Pro, 52E8FAD0-7743-4F85-AA2E-26E4C1275F38 64 | ``` 65 | 66 | Example run with no open simulators: 67 | 68 | ```bash 69 | $ nine41 70 | Fixing status bars... 71 | ❌ No simulators are running. Launch the iOS simulator first. 72 | ``` 73 | 74 | ## Automation with Xcode build phases 75 | 76 | As described [in this post](https://www.jessesquires.com/blog/2020/04/13/fully-automating-perfect-status-bar-overrides-for-ios-simulators/), you can automate your perfect status bars using Xcode build phases. 77 | 78 | 1. Add the Swift package to your Xcode project 79 | 2. Add a "Run Script" build phase with the following: 80 | 81 | ```bash 82 | /usr/bin/xcrun --sdk macosx swift run --package-path "${BUILD_ROOT}/../../SourcePackages/checkouts/Nine41" 83 | ``` 84 | 85 | 3. Build and run. Note that simulators must be booted for the script to work, which means the very first run may not produce results but the subsequent runs will. 86 | 87 | ## Contributing 88 | 89 | Interested in making contributions to this project? Please review the guides below. 90 | 91 | - [Contributing Guidelines](https://github.com/jessesquires/.github/blob/master/CONTRIBUTING.md) 92 | - [Code of Conduct](https://github.com/jessesquires/.github/blob/master/CODE_OF_CONDUCT.md) 93 | - [Support and Help](https://github.com/jessesquires/.github/blob/master/SUPPORT.md) 94 | - [Security Policy](https://github.com/jessesquires/.github/blob/master/SECURITY.md) 95 | 96 | Also, consider [sponsoring this project](https://www.jessesquires.com/sponsor/) or [buying my apps](https://www.hexedbits.com)! :v: 97 | 98 | ## License 99 | 100 | Released under an [MIT License](https://opensource.org/licenses/MIT). See `LICENSE` for details. 101 | 102 | > **Copyright © 2019-present Jesse Squires.** 103 | --------------------------------------------------------------------------------