How to Build a macOS Menu Bar App With React Native

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

This lesson preview is part of the Building React Native Apps for Mac course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to Building React Native Apps for Mac, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course Building React Native Apps for Mac

Great. We have made it into one of my favorite lessons in the entire course, which is turning our application into a status bar app. Right? The status bar app is one of these apps that lives on the Mac OS status bar. This is my own application. So we're going to take our app and we're going to put it in there. Now this lesson is going to be a little bit more complex than the lessons that we have seen so far. So gonna have to bear with me for a little bit. It's gonna get fairly technical. So we're gonna start by migrating our application to Swift. Now this is not 100% necessary in order to do this, but the truth is I don't know objectivity and I also don't want to learn objectivity because it's quite a complex language. And so if this a lot more similar to my experience, which is with JavaScript and C like languages, so we're just gonna I tend to stick with that. So let's just get to it. I'm gonna go into Xcode into my explorer and I'm gonna delete a bunch of files. I'm gonna delete the app delegate, both of them, the view controllers and the main. m file. I'm gonna move into the trash. Now I'm gonna create an app delegate.swift file. So I'm just gonna click right click in here. I'm gonna create new file. I'm gonna select a swift file as the name is app delegate. It's adding to the Mac West target, which is what I want. Then you will see this Objective C bridging header. You want this. Basically the bridging header is gonna allow your Swift code to access Objective C libraries. So yes, we definitely want this to work. Then I'm gonna quickly go into the bridging header and I'm just gonna take what I put on the lesson, which are all the react and react native files that need to be run. Need to be there for react native to run. And then I'm just gonna go back into my app delegate file. I'm just taking the course from the course, the contents. And let's just go over this as a bunch of code. But we have first a few imports. These are frameworks, native frameworks used by MacOS. Also by iOS. So foundation has a bunch of utility classes and files. COCOA is the UI system. And then we're gonna go into the NS application main. So this is the entry point for our app. Whenever the app starts, the OS gonna call this class. And this class is extending to objects, NSO object, NS object, which is kind of the basic object class for all the MacOS files and NS application delegate. So not worry too much about that. Then comes the action fun part. So in order to put our application inside of the status bar, we need what is called a popover. A popover is just kind of these windows that have that are tied to a certain piece of element on the screen. And the popover, maybe you have seen it, there are some applications that have popovers in different places. But that's what we're gonna use it. That's gonna be the root container of our app. Next we have NS window, which is something that we will use for debugging our application. We'll go over it in a little bit. But this is just one more window. It doesn't have anything to special about it. Then we have NS status item. The status item is each one of the icons. Right. So you if you want to put something on the status bar, you need to use this item, this class. Then we have our application did finish launching method. Right. So once the application has completely started or the container for application has started , this is gonna be called and this is where we initialize not only our bundle, you know, react native initializes or bundle or loads or JavaScript, but we can actually create different stuff. We can create the popover, we can create the window, et cetera, et cetera. So this bunch of code is more or less the same as the objective C part that was in there before. So you don't have to worry too much about it. And further lessons, we will modify this even more. But for now, all you need to know is that these loads are JS code. And this creates our city root view, which is the main element for react native applications. Great. So let's move on a little bit. Now we have our NS popover. We actually create in the instance of our popover. We give it some properties. We give it a width and a height. We say that it should animate. We give it a behavior. That means where the transient behavior means whenever I click on some other application or I interact with any other element, it should close itself. And we're gonna pass our root view controller. That is, if you see here where we have placed the instance of react native, we're gonna assign that to the popover. Right. So we're gonna say this popover is gonna run react native. Next, we have our status bar item. We're just gonna create one status bar and we're gonna give it a width. And we 're gonna bind or we're gonna from this status bar item internally, it has a button reference. So we're gonna extract that button reference. And this is just this just weird syntax from so if you can just use it. And we're gonna say the action for that button. We 're gonna select a function that we defined down here, which is gonna take care of opening and closing our popover. And we're gonna give it a title. For now, that's fine. Later, if you want, you can put an icon in there. But for now, we're just gonna put some text. And this is what I was saying before that we're gonna use an extra window. The problem is if we would just use the popover to develop our application, then it's fairly annoying because I might be doing something, right? And the popover opens. And then I go back into VS code and it's just gonna close. Right? So it's gonna be this back and forth when I'm gonna click, gonna change the code, I have to move my mouse here, click again. It's just a waste of time. So the only thing that I'm doing is whenever there is a debug flag, this is a macro. And that is whenever the application is run in dev mode, we're just gonna create an extra window. And it's basically gonna mirror whatever the popover should show. Right? So this is just useful. This is just very, very useful for development purposes . Great. Then we have our toggle popover function. This small modifier says this function is available or it's gonna be breached into Objective-C, which is necessary because a lot of this UI components, they're still written in Objective-C, right? So there needs to be some way to connect Swift functions and Objective-C code. So this is how you expose a Swift method into the Objective-C world. So the popover takes a sender, which is an any object or maybe no, doesn't matter. And whenever this function is called, we're gonna do the same trick that we did before. We're gonna extract the button from the status bar item. And we 're just gonna toggle it, right? Like we're gonna ask if the popover is shown, then we want to close it. If it's not shown, we want to show it. And we're gonna show it with this method, which is the relative two. So it takes the bounce of our button. So this depending on your screen resolution, this will have certain bounce. We want to show it there , right? And what preferred edge it maybe can show to the left to the right. It doesn't matter. And then we're finally just gonna tell, you know, put the focus on there, right? Whatever program the user was focused, had focus, just put it in the background. Now the user should focus that window. Great. So before we can move on, there's still a few things that we need to clean up. I'm gonna go into the main storyboard. And because we deleted the Objective-C code, there was a bunch of objects that were being used there before that we no longer need. So I'm just gonna select this window controller. I'm gonna delete it. This view controller as well. And now it should be left with this application scene. If I select the app delegate, and I'm gonna reopen this side menu. This one should point or was pointing to the previous app delegate file or the previous app delegate class. But because we have deleted it, now it kind of has a null reference. So I'm just gonna do it one more time. I'm just gonna type it. And Xcode is internally gonna try to find again the correct app delegate file. Right? It's very obscure. You don't have any clue that that might be wrong. But we need, we just need to do that. And so let's just try to run the app just to see what it gives us. And you will see, I'm just gonna filter for the errors. There is a bunch of errors, right? There's a lot of errors. But basically everything comes down to it cannot find a bunch of the Swift libraries. And this is just some very technical, internal detail. You don 't need to worry too much on the why. But we're just gonna go into our building app folder. I'm gonna go into build settings. And I'm gonna search for that code stripping. And I'm gonna say yes, right? Whatever Swift code our application is not using, I want to get rid of it. And I'm also going to do, I'm also going to search for always embed Swift standard libraries. And I'm going to change that to yes. Right? So it means if we're packaging for an older version of macOS, and I don't know what which one was the last one, Mavericks, I think, or no, Mavericks, the old one. Doesn't matter. The old version might not have the Swift libraries in the OS exposed to the applications. So we're just gonna say just embed them, send the app with the Swift libraries in them. Great. So let's just try to run the app now. Let's see what happens. We're still getting some errors. I am just going to clean the build one more time just to see what happens. I'm going to hit run again. Sorry , there was one small detail that I overlooked. When I created my application, I did it on an Intel machine. And the M1 machine, the Apple Silicon Max, they weren't out yet. So there was no problem building the app. If you try to build the app after this step, you will still get a lot of unknown errors. And that is because React Native doesn't support Apple Silicon yet on the macOS. So we need to exclude the architecture from our build. So what I'm going to do is once again, I'm going to go into the file explorer, click on the root object. I'm going to go into the macOS target. And here you can see if you search for architectures, you will see the standard architectures. And this is going to create a universal app, right? So it's going to compile it for Apple Silicon and Intel, which is what we don't want because the Swift libraries are not working properly. So I'm going to click on that. I 'm going to go into other. I'm going to delete the standard architectures. And I'm going to create a new one that just says x86 underscore 64. And once you do that, you should be able. Sorry, I didn't save it. Just try it again. X86. Okay, good. So now once you have changed that, you will notice that this will change and they will say my Mac on the raw set, right? So this is going to run as an interlap with the macOS emulation. So this is, I 'm expecting this to improve at some point. I expect my quest on Apple Silicon to work perfectly. But for now, we're just going to do this. Now I'm going to first clean my bill folder, and I'm going to run the app one more time. And let us see what it says. I'm expecting this build to fail. And this one small detail. And but that's going to be the last one we need to take care of. Great. So our application compiled just fine. And it actually tried to start. But once it started, it crashed. And you can see the error in here. This says unexpectedly found nil while wrapping an optional value. So if you take a look into this code, here you will see this and if, but it's not normally if this kind of this hashtag if debug, this is what is called macro on macro processor. So what this does is before compiling the file, it checks for the flag. And it's going to dynamically remove code before the code is actually compiled. So what's happening is that we're missing this debug flag , which usually it's already there for the objective C compilation, but it's not there for the Swift process. So this is something that we need to do ourselves. And we're going to go do this in the again, the explorer, I'm going to build settings. And I'm going to look for other sweet flags. And here's going to be a bunch of sweet flags, mostly for the compilation process. And in here, we're going to add a new one. And we're going to say D debug. This is not a typo. This meant to be together. And now let's close our app. And let's try running it again. And let's cross our fingers. It works. Well, mostly . Let us check what the error is in here. Oh, yeah, right. This one's more detail because I copied and pasted the code. I forgot about it. Let me just quit the app. And let me go back into my app delegate. Here you can see, I just use the name from previous application. But this one needs to match. If you go into the index dot JS file, which is on the road of your folder, here you will see you're using this app registry, which is coming from React Native and your register in a component, which is our entire app. But this app name is coming from the app dot JSON file. And it's this one. So you'll see it slightly different. So you know, copy and paste in can go bad sometimes. So we're just gonna correct the name and try to run our application once again. And there you go. So right now it doesn't look too special. Right? It's the same app that we have been building all this time. But if you take a look into the status bar, you will see there's a new item. And if I click on the new item, our application has just started. So that's how you run a React Native application in the status bar. [BLANK_AUDIO]