iOS 14 Tweak Development: Beginner Tutorial


Tweak development can be hard, especially when you realize that all tutorials on it are from 2015. That's why I decided to make an updated tutorial.
I assume you know Swift or understand some programming concepts. If so, great!
If you feel stuck or get some errors that are not described in the tutorial, verify you've done everything correctly. If nothing works, try asking for help on Theos Discord server or reach me out on my own
Credits: Theos, Orion Docs
Introduction
Choose your Pokemon
Operating system
When it comes to tweak development, you can use multiple OSes: Windows, Linux, MacOS and iOS. And of course, we, with expensive iPhones don't have a Mac, and you most likely have either Windows or Linux. If you have a Mac, then you're in luck, because it's the platform where the least amount of problems arise.
If you're on Windows, I suggest setting up WSL or dual-booting Linux.
You can also create tweaks using iOS, but you won't be able to code a huge project using only your fingers :)
Language

You need to choose between Obj-C and Swift. Both work great, but do keep in mind Obj-C is harder for beginners. The key advantage of Obj-C is that it has lots of open-source tweaks written in it, while Swift only has a few. If you choose Swift, you'll have to translate a lot of stuff from Obj-C to Swift. But Swift is much easier to read and learn, so if you've programmed only a little/never, I would suggest using it. Also, if you have a MacOS machine, you have a bonus of being able to use Xcode - an IDE with auto-completion and other neat features. We'll use Swift in this tutorial.
IDE
If you use Mac, use Xcode. If you are using Linux / Windows, use VSCode or any other IDE you prefer. I will use VSCode throughout this tutorial.
Installation
Commands, commands, commands
First we need to install Theos - a tool for writing tweaks. Follow the instructions here.
If you are on Linux / WSL, make sure you have Swift installed.
Since we are using Swift, we also need a tool called "Orion". On your jailbroken device add the Chariz repo (https://repo.chariz.com/) and install the Orion Runtime package (pick iOS 12-13 or iOS 14 depending on your version).
Then, on your computer, inside your Theos installation directory (to change the current directory run cd $THEOS
) and switch to the orion
branch by running git fetch && git checkout orion && git submodule update --init
.
Sometimes, builds will fail due to an old toolchain. Make sure to install it:
rm -r $THEOS/toolchain/*
curl -#L https://github.com/kabiroberai/swift-toolchain-linux/releases/download/v2.1.0/swift-5.6.1-ubuntu20.04.tar.xz | tar -xvJ -C $THEOS/toolchain
You should have all tools installed to finally create a new project!
New project
I thought I would just need to click "Install"
Now that you have all the required tools installed, you can finally create a new project. To do so, run the nic.pl
script located at $THEOS/bin/nic.pl
( just run $THEOS/bin/nic.pl
inside the directory you want the project to be created in, use cd
to switch directories).
$THEOS
directory. Create project ins a permanent directory, such as ~/Developer
.You should see a message like this:

Select iphone/tweak_swift
, I had to enter 18 in my case.
Next, name the project StatusTimeFormatter
, we'll create a tweak that changes the time in Status Bar to a date, like 8-23-22 5:31 PM
. For package name use this template com.yourname.statustimeformatter
(of course replace yourname with Β your Β name). I have net.sourceloc.statustimeformatter
, because I own a domain sourceloc.net
.
For author you can press enter to use a default value or enter your own. Leave the default value of MobileSubstrate Bundle filter
(press enter) and same for apps to terminate. You should end up this something similar:

And the project should be generated in the directory you were just in.
Let's take a look. Open VSCode / Xcode and open the project folder.

Sources
contain your source files for the tweak. But wait, why are there two folders: StatusTimeFormatter
and StatusTimeFormatterC
? I thought I'll be writing in Swift?

That is because some stuff requires importing Obj-C headers. Basically, headers define names of functions, but they don't tell what exactly the function does.
BUT, we can also write "headers" in Swift, though it's harder that Ctrl+C Ctrl+V from some open-source project on GitHub :) So you generally want to write headers in Tweak.h
, but more on that later.
StatusTimeFormatter
contains Swift code. This is the place you will spend 90% of your time while working on a tweak.
.gitignore
you can read more about it here.
.theos
is the folder required for Theos
, you don't need to touch it.
control
file describes what name should the package use and what version it's on.
Makefile
tells Theos how to compile your tweak, how to install, which SDK to use and more.
Package.swift
is a file that tells Swift of what targets you project consists. Otherwise Swift wouldn't be able to tell which files need to be compiled. We'll modify this file in the future to support Preferences.
StatusTimeFormatter.plist
determines in which apps should the tweak inject to. In this tutorial we'll only target SpringBoard, so you don't need to change it.
sourcekit-lsp (optional)
Scary words!
βTheos has a trick up its sleeve: with some basic configuration, you can get full code completion while working on Orion tweaks on any OS, in most editors! Enabling this is simple:
- Install the sourcekit-lsp plugin corresponding to your editor of choice. (If youβre using Xcode, it has built-in SourceKit support so skip this step.)
- Run
make spm
inside your project to generate metadata about your project, which is needed by Swift Package Manager/SourceKit. - Open the project folder in your chosen editor.
Just like that, you should have code completion!
Compiling
Code is cool and all, but how do I turn it into a tweak?
Great question. Remember you installing Theos and Orion? These are the tools responsible for compiling the tweak. To start compilation run make do
inside the project directory (If you use VSCode, you can use it's terminal to do so). But oh no! Errors!

make do
tries to install the tweak to device, but would it know on which device it needs to install it onto? We need to tell Theos the IP and port of your device. Get the IP of your iPhone:
- Go to Settings > Wi-Fi.
- Tap the information icon to the right of the Wi-Fi.
- Set the
THEOS_DEVICE_IP
environment variable on your computer in your "shell configuration file" (like.bashrc
,.zshrc
). You can do so by adding the following line to the file at the bottom:export THEOS_DEVICE_IP=enter_ip_here
. Replaceenter_ip_here
with phone's ip. Restart the terminal to apply changes
THEOS_DEVICE_IP=enter_ip_here
.Try running make do
again and after a few seconds it will ask for password. Enter alpine
(Unless you changed it on your device). It might ask it two times.
make do
, you can save the ssh password with ssh-keygen
and ssh-copy-id root@IPHONE_IP_HERE
And here you go, your first "An awesome MobileSubstrate tweak"! Well, not quite, it says "An awesome Orion tweak", but I assume you understood the joke.

Now it's FINALLY time for some research and coding
Research
Sherlock Holmes Simulator 2022
Before you want to modify something, you first need to know how and what exactly. In our case we want to modify the status bar time text to something else. But how? We can try to check every single text in the whole iOS if it matches the current time and if it does, then only apply the needed changes. But that's just awful. What if we could somehow get a class name of a specific object and hook it? What if ... :) Introducing, FLEXall. Install it on your device from this repo https://dgh0st.github.io/.
This tweak allows you to view how elements (UIViews) are placed on SpringBoard and any other app. Try long tapping the status bar and you will see a menu appear like this:

Tap on select
and tap on something, you will notice a border around the item you selected appear.

Pretty cool, right?
We want to change the time label, let's click it

Notice the _UIStatusBarStringView
, it's the class name we want to hook. But before jumping to coding, we also need to know which methods and properties it has. Tap on views
. This will bring the hierarchy of UIView
s rendered on the screen. The time label you selected before will be highlighted in the list. Click the info button.

You will see all properties this specific class has, as well as properties of it's subclasses. Great. Now let's find something useful we will use in our code.
(The following image has an error, these are called "Superclasses", not "Subclasses")

Every minute iOS will try to update the time add one minute to text. It should probably call some method on the label to "update the text" to something else. Let's try looking for a variable that stores the text. Oh wait, it's right there - text
, with current value 22:25

While we don't use Obj-C, we still need to understand some of it's concepts, one of which is setter. If a class has a non-read-only @property
, it also has a method named setNAME
. We want to replace the method setText
. We'll do all of that in the next chapter!
Hooking
Just like fishing, but not at all
Let's write our first class hook. Inside of Tweak.x.swift
add the following code below the already existing imports:
class StatusBarTimeHook: ClassHook<_UIStatusBarStringView> {
}
Here, we create a new hook on _UIStatusBarStringView
, allowing us to override some of it's methods. The name StatusBarTimeHook
can be whatever you please, but naming them with an ending Hook
is the best practice.
Let's try replacing the previously mentioned setText
method by creating a function inside the hook:
func setText(_ text: String) {
orig.setText("Hello world!")
}
_ text
. Why is it written like so? Because when overriding an Obj-C method you still need to match it's signature. The signature for this method is - (void)setText:(id)
- you can check the signature for any method in FLEXall.Another mysterious thing in this code is orig.setText("Hello world!")
. It calls the original method of the instance, but with different arguments. So to call the original method with the same arguments you can use orig.setText(text)
, where text is the passed parameter from the hooked setText
.
Try building and see the result! Haha, I got you there, didn't I :)? It fails:

How would Swift know what is _UIStatusBarStringView
? We need to add an @interface
inside Tweak.h
:
#import <UIKit/UIKit.h>
@interface _UIStatusBarStringView : UILabel
@end
Here, we also import UIKit and tell, a _UIStatusBarStringView
class and that it is a subclass of UILabel
.
Now run make do
, it should install it on the device and this time it will work:

... sort of. A label we want to change successfully changes, but so do the other status bar labels. Why so? Let's see why.
The only reason why our code would change the labels is if they are a subclass of _UIStatusBarStringView
and if their method "setText" was called. Use FLEXall
to check if our theory is true: active the menu, click on the carrier text, and you will see something like this:

Turns out, that's the case! Now the question is: how do we separate those labels? The recommended way is to check if the superview is some specific class, but we are here for educational purposes, so why not just check if there's a colon (:
). Stupid, but it will work.
func setText(_ text: String) {
if text.contains(":") {
orig.setText("Hello world!")
} else {
orig.setText(text)
}
}

Cool! Now all we have to do is replace the Hello world with the actual data. Using https://nsdateformatter.com/ we can generate any formatter we want. And to use it in Swift we can use the code I found after one google search:
func setText(_ text: String) {
if text.contains(":") {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm"
orig.setText(formatter.string(from: Date()))
} else {
orig.setText(text)
}
}
And here's the final result:

The final thing is changing the Control
file to prepare the tweak for distribution. Open it up and edit the fields you want. Now run make do FINALPACKAGE=1
and you should now have the .deb
inside the packages/
folder.
If you want to support arm64e, you have to use MacOS.
Bonus challenge
Change the color of text to yellow
.
Using FLEXall
we can view methods of _UIStatusBarStringView
's superclass - UILabel
. Tap on UILabel
in the top bar. (Because the subclasses have all methods from their superclass, we might be able to find something useful and use it in our code). Let's try searching for color
, and you will find a method called setTextColor
with signature - (void)setTextColor:(id)
. Override this method and we get this result:
import Orion
import StatusTimeFormatterC
class StatusBarTimeHook: ClassHook<_UIStatusBarStringView> {
// orion:new
func isDateLabel(_ text: String) -> Bool {
return text.contains(":")
}
func setText(_ text: String) {
if isDateLabel(text) {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm"
orig.setText(formatter.string(from: Date()))
} else {
orig.setText(text)
}
}
func setTextColor(_ color: UIColor) {
if isDateLabel(target.text ?? "") {
orig.setTextColor(.yellow)
} else {
orig.setTextColor(color)
}
}
}
// orion:new
is not just a comment, it tells Orion, that this method is user defined and should not be used as an override of an existing method.

Conclusion
π π π
Congrats, you made it to the end! We learned how to setup the project, analyze the structure of iOS views, write hooks, troubleshoot, understand some concepts of Obj-C and got a final result - a tweak ready for distribution.
Go ahead, post the source code on GitHub so everybody now knows that you are now a tweak developer :)
You can check the source code for this project here.
Credits: thissupermegaperson on discord (typo)