macOS Menu Bar App (Code Along) | SwiftUI, Xcode

Flo writes Code
4 May 202228:47

Summary

TLDR本视频教程向观众展示了如何创建一个Mac OS菜单栏应用程序,并与用户的剪贴板进行交互。视频由Flo主持,他首先回顾了之前在Twitter上发布的关于这个菜单栏应用程序的想法,并获得了积极的反响。接着,Flo逐步演示了如何使用Xcode创建项目,设置菜单栏图标,并实现点击菜单栏图标时弹出包含链接列表的弹出窗口。他还详细介绍了如何使用SwiftUI和Core Data来管理应用程序的数据,并展示了如何将链接保存到剪贴板以及如何退出应用程序。此外,Flo还分享了他在开发过程中的一些思考,包括对Swift UI预览功能的看法,以及如何手动生成类和属性。视频最后,Flo鼓励观众订阅频道,点赞视频,并在评论区分享他们的应用想法或其他教程建议。

Takeaways

  • 🎓 学习如何创建一个macOS菜单栏应用,并了解如何与用户的剪贴板进行交互。
  • 📱 通过点击菜单栏中的图标,可以弹出一个列表,用户可以从中选择链接并复制到剪贴板。
  • 🛠️ 使用Xcode创建项目,并选择使用Core Data,随后根据需要对项目进行清理和配置。
  • 📝 创建一个名为`ql link`的Core Data实体,包含id、title和url属性。
  • 🔄 通过Swift UI创建用户界面,并使用`NSHostingController`来展示。
  • 📂 利用状态栏控制器管理菜单栏图标,并定义点击事件来显示或隐藏弹出视图。
  • 🔗 通过构建一个`NSPopover`和设置其行为为`transient`,来管理弹出视图的显示和隐藏。
  • 💾 通过Core Data的`NSManagedObjectContext`来管理和获取链接数据。
  • 🔍 使用Swift UI的`VStack`和`HStack`来布局视图中的链接列表和其他UI元素。
  • 🔑 为链接列表中的每个链接项创建一个可点击的文本链接,并在旁边放置复制按钮。
  • 🚫 移除主窗口,使应用仅作为一个菜单栏图标存在,通过设置`Application is agent`为`yes`实现。
  • 📌 强调了应用的实用性,鼓励观众订阅频道、点赞视频,并在评论区留下反馈或其他应用创意。

Q & A

  • 在视频中,Flo提到了一个用于macOS的菜单栏应用,这个应用的主要功能是什么?

    -这个应用的主要功能是在macOS的菜单栏中显示一个图标,用户点击后可以查看和操作一系列链接。用户可以通过文本框输入链接的标题和URL,然后点击按钮将链接复制到剪贴板。此外,应用还包含退出按钮,允许用户退出程序。

  • Flo在视频中提到了哪些步骤来创建一个macOS菜单栏应用?

    -Flo提到了以下步骤:1) 创建Xcode项目并初始化Core Data;2) 清除不需要的Core Data配置;3) 创建菜单栏应用的用户界面;4) 实现点击菜单栏图标后显示的弹出视图;5) 配置状态栏控制器以响应用户点击;6) 实现链接的保存和复制到剪贴板的功能;7) 优化应用界面和布局;8) 配置应用在启动时不显示主窗口,仅显示菜单栏图标。

  • 在创建菜单栏应用的过程中,为什么Flo选择不使用Swift UI预览功能?

    -Flo选择不使用Swift UI预览功能是因为在macOS上,Swift UI的实时预览功能并不像在iOS应用中那样工作。在macOS上,点击Swift UI预览的播放按钮会打开一个新的应用窗口来显示组件,而不是在Xcode内部实时显示,Flo认为这样的处理方式不如直接运行应用来得方便。

  • Flo在视频中提到了如何将链接复制到剪贴板,这个过程涉及到了哪些步骤?

    -将链接复制到剪贴板的过程包括:1) 使用NSPasteboard的general实例;2) 调用clearContents方法清除剪贴板原有的内容;3) 使用setString方法将链接的URL作为字符串复制到剪贴板。

  • 在macOS应用开发中,为什么有时候需要先清除剪贴板的内容,然后再复制新的内容?

    -在macOS中,有时需要先清除剪贴板的内容,是因为NSPasteboard的setString方法或其他类似方法在没有先清除剪贴板的情况下可能不会生效。因此,为了确保新的内容能够被正确复制,需要先调用clearContents方法。

  • Flo在视频中提到了使用Core Data进行数据管理,那么在创建Core Data模型时,他创建了哪些属性?

    -在创建Core Data模型时,Flo创建了一个名为'QLLink'的实体,它包含了三个属性:id(UID类型)、title(字符串类型)和url(字符串类型)。

  • 在macOS的状态栏应用开发中,如何实现点击状态栏图标后显示一个弹出视图?

    -在macOS的状态栏应用开发中,可以通过创建一个NSPopover实例,并在状态栏控制器中配置该实例,然后在用户点击状态栏图标时,使用show(relativeTo:of:preferredEdge:)方法来显示弹出视图。

  • Flo在视频中提到了如何隐藏应用的主窗口,只保留菜单栏图标,这是如何实现的?

    -为了隐藏应用的主窗口,只保留菜单栏图标,Flo在Xcode的项目设置中,针对应用的target,打开了Info.plist文件,并添加了一个键为'Application is agent'的新值,将其设置为'YES'。这样,应用在启动时不会显示主窗口,只有菜单栏图标会显示。

  • 在视频中,Flo创建了一个名为'PopOverView'的Swift UI视图,这个视图的作用是什么?

    -在视频中,'PopOverView'是用来作为菜单栏应用点击后弹出视图的Swift UI视图。它包含了显示链接列表的界面,以及用于添加新链接的文本框和按钮。

  • Flo在实现复制链接到剪贴板的功能时,遇到了哪些问题,他是如何解决的?

    -Flo在实现复制链接到剪贴板的功能时遇到了一个问题,即直接使用NSPasteboard的setString方法并不能将内容复制到剪贴板。他通过先调用clearContents清除剪贴板原有的内容,然后再使用setString方法复制新的链接,解决了这个问题。

  • 在macOS菜单栏应用中,如何确保应用在用户关闭主窗口后仍然运行?

    -在macOS菜单栏应用中,可以通过设置Info.plist中的'Application is agent'键为'YES',使得应用在用户关闭主窗口后仍然在后台运行,并且只保留菜单栏图标。

  • Flo在视频中创建了一个状态栏控制器,这个控制器的作用是什么?

    -状态栏控制器的作用是管理菜单栏中的图标和与之相关的行为。在Flo的视频教程中,状态栏控制器负责创建和配置状态栏图标,以及定义点击图标后显示的弹出视图和相关的交互逻辑。

Outlines

00:00

📱 创建MacOS菜单栏应用

本段介绍了如何创建一个MacOS菜单栏应用,并与用户的剪贴板进行交互。Flo在昨天的直播后决定制作一个长视频,展示并解释他几天前编写并已在Twitter上发布的菜单栏应用。该应用允许用户点击菜单栏中的图标,通过文本框保存链接,并通过按钮将链接复制到剪贴板。Flo还提到了如何清理Xcode项目中的不必要组件,并创建了用于存储链接数据的Core Data模型。

05:09

🔗 构建和配置菜单栏项目

在构建成功后,Flo创建了包装属性以简化数据访问,并开始构建菜单栏项目。他创建了一个应用代理类来注册菜单栏项,并设置了一个弹出窗口,该窗口将在用户点击菜单栏项时显示。Flo还创建了一个状态栏控制器,用于管理菜单栏中的图标和弹出窗口的行为,包括显示和隐藏弹出窗口。

10:18

🛠️ 实现状态栏控制器

Flo创建了一个状态栏控制器类,用于管理状态栏图标和弹出窗口。他解释了如何初始化状态栏、状态栏项,并为其添加了一个按钮,该按钮在用户点击时会触发一个动作。Flo还展示了如何在用户点击按钮时显示或隐藏弹出窗口,并强调了在状态栏控制器中实现这一行为的重要性。

15:23

📑 配置应用代理和运行应用

Flo在应用代理中添加了一个状态栏控制器,并注册了应用代理,以便在应用启动时自动设置菜单栏项。他运行了应用并展示了菜单栏中的图标和弹出窗口。尽管弹出窗口的对齐和样式需要改进,但基本功能已经实现。Flo还提到了如何整理项目结构,以便于管理和查找相关代码文件。

20:23

🔄 创建和管理快速链接

Flo在弹出视图中创建了一个垂直堆栈,用于展示用户保存的快速链接。他使用了Core Data的托管对象上下文来获取链接数据,并使用Swift UI的列表来展示每个链接。Flo还展示了如何通过按钮将链接复制到剪贴板,并解释了为什么需要先清空剪贴板的内容。此外,他还提到了如何关闭弹出窗口,以便在用户点击复制按钮后提供更好的用户体验。

25:29

🖌️ 调整UI并添加新链接功能

Flo对UI进行了调整,为输入区域添加了内边距和固定宽度,以改善布局和外观。他创建了两个文本框,用于输入新链接的标题和URL,并添加了保存和退出按钮。Flo确保了在用户输入有效数据后,可以通过保存按钮将新链接添加到Core Data存储中。他还提到了如何清除文本框,以便用户连续添加多个链接,并展示了如何在不显示主窗口的情况下运行应用,使得应用仅以菜单栏项的形式存在。

🎬 完成视频和未来计划

在视频的最后,Flo总结了整个教程的内容,并鼓励观众订阅频道、点赞视频,并在评论区留下反馈或其他应用想法。他强调了这个菜单栏应用的实用性,并提出了可以添加的额外功能,如保存不同类型的数据。Flo希望观众能从这个轻松的教程格式中获得乐趣,并学习到有用的知识。

Mindmap

Keywords

💡macOS 菜单栏应用

macOS 菜单栏应用是一种常驻在 Mac 电脑屏幕顶部菜单栏中的应用程序,用户可以通过点击菜单栏中的图标来快速访问应用功能。在视频中,Flo 正在创建一个 macOS 菜单栏应用,该应用允许用户管理快速链接,并通过点击菜单栏图标来访问这些链接。

💡粘贴板

粘贴板是操作系统提供的一个临时存储区域,用户可以将文本、图片等数据复制到粘贴板,然后在其他地方粘贴使用。在视频教程中,Flo 展示了如何通过点击应用中的按钮,将链接复制到用户的粘贴板。

💡Xcode

Xcode 是苹果公司开发的集成开发环境(IDE),用于开发 macOS、iOS、watchOS 和 tvOS 的应用程序。在视频中,Flo 使用 Xcode 创建和演示了 macOS 菜单栏应用的开发过程。

💡Core Data

Core Data 是苹果公司提供的一个对象图形映射(ORM)框架,用于在 macOS 和 iOS 应用中存储数据。在视频中,Flo 选择了使用 Core Data 来管理应用的数据,尽管后来为了简化示例,移除了 Core Data 相关的代码。

💡SwiftUI

SwiftUI 是苹果公司推出的一种用于构建用户界面的编程框架,支持 macOS、iOS、watchOS 和 tvOS。在视频中,Flo 使用 SwiftUI 来设计菜单栏应用的界面,提供了一种声明式的方式来创建和管理用户界面。

💡NSPersistentContainer

NSPersistentContainer 是 Core Data 框架中的一个类,用于封装 Core Data 堆栈的核心组件,包括管理对象上下文和持久化存储。在视频中,Flo 配置了 NSPersistentContainer 以确保自动合并来自父级的更改,尽管这个设置对于不使用 CloudKit 的应用来说可能不是必需的。

💡NSPopover

NSPopover 是 macOS 中的一个类,用于创建和管理弹出窗口,这些窗口可以包含额外的界面元素或信息。在视频中,Flo 使用 NSPopover 来实现点击菜单栏图标后显示的快速链接列表。

💡NSStatusBar

NSStatusBar 是 AppKit 框架中的一个类,代表 macOS 状态栏,允许开发者在其中添加自定义的菜单栏项目。Flo 在视频中创建了一个 NSStatusBar 项目,并为其添加了一个点击事件,用于触发显示 NSPopover。

💡NSPasteboard

NSPasteboard 是 Foundation 框架中的一个类,用于访问和修改系统粘贴板的内容。在视频中,Flo 演示了如何使用 NSPasteboard 来复制链接到用户的粘贴板,这是通过调用 `NSPasteboard.general` 并使用 `clearContents` 和 `setString` 方法实现的。

💡UUID

UUID(Universally Unique Identifier)是一个标准化的唯一性标识符,用于在分布式系统中无需中央协调就能保证唯一性。在视频中,Flo 使用 UUID 为快速链接生成唯一的标识符,确保每个链接都有一个唯一 ID。

💡App Extension

App Extension 是 macOS 和 iOS 应用程序的一部分,它们可以提供额外的功能,如今天小部件、消息应用贴纸包等。在视频中,Flo 提到了将应用程序作为代理(Agent)来运行,这意味着应用将在后台运行,并且不会显示一个传统的主窗口。

Highlights

创建了一个Mac OS菜单栏应用程序,允许用户通过点击菜单栏图标来交互。

应用程序在菜单栏中显示一个SF图像图标,点击后不改变当前活动的应用程序。

提供了两个文本框供用户保存链接,并通过按钮将链接复制到剪贴板。

演示了如何通过Swift UI创建和管理应用程序的用户界面。

介绍了如何在Xcode中创建项目,并选择使用Core Data。

展示了如何清理和配置项目视图,以及如何移除不需要的Swift UI预览功能。

创建了一个名为'ql link'的Core Data实体,用于存储链接的ID、标题和URL。

手动生成类,而不是使用自动生成的代码,以提供更多的控制。

创建了一个状态栏控制器,用于管理菜单栏图标和弹出菜单的行为。

介绍了如何使用NSPopover和NSViewController来实现菜单栏的弹出功能。

展示了如何在应用程序代理中注册菜单栏项,并在应用启动时设置弹出内容。

通过实现一个按钮动作来控制弹出菜单的显示和隐藏。

创建了一个Swift UI视图,用于显示和管理快速链接,并使用Core Data进行数据存储。

介绍了如何使用NSPasteboard将字符串复制到剪贴板,并处理相关的API细节。

提供了一个示例,展示了如何在用户点击保存按钮时将新链接添加到Core Data存储中。

展示了如何通过设置应用程序为代理来隐藏主窗口,使应用程序仅在菜单栏中运行。

讨论了应用程序的潜在扩展性,包括添加更多特性和保存不同类型的数据。

鼓励观众订阅频道、点赞视频,并在评论区留下反馈或其他应用程序的创意。

Transcripts

play00:01

in this video you will learn how  to create a mac os menu bar app  

play00:06

and how to interact with the user's paste

play00:08

board hey this is Flo and after yesterday's  successful first stream at least yesterday  

play00:21

as of the date of this recording i figured i  might as well do another koda long style video  

play00:28

and this time i'm doing or i'm  working on a menu bar app for mac os  

play00:33

that i've already written actually a  few days ago and i've posted about it  

play00:37

on twitter so if you don't follow me  on twitter yet go ahead and do that  

play00:42

and people on twitter seem to like the idea so  i figured i would just do another color long  

play00:47

video recreating the app and explaining my thought  process explaining how i work and how this app can  

play00:54

be made so you've already seen how it works there  will be a sf image in your menu bar and when you  

play01:01

click on it you will notice that the active app  does not change to whatever our app is called  

play01:08

it stays as xcode in our case then there will be  a list of links which we can save through these  

play01:16

two text fields and then if we hit one of these  buttons the link will be copied to the clipboard  

play01:23

of course we can also quit the app with the  quit button i'll demonstrate that later in  

play01:28

the app that we will actually build okay so to get  started i already created an xcode project and i  

play01:33

selected use core data because of that there  is always a bunch of stuff that we need to  

play01:38

clean up first so let me get rid of most of those  things so in our view body let's say welcome to  

play01:48

menu bar links that's what i call the project  and then let's give that a ton of padding  

play01:56

let's remove the add item function let's remove  the delete items function let's remove this item  

play02:02

formatter and let's also just remove that preview  because we will not be using swift ui previews in  

play02:08

this project i don't like using them on the mac  because the live preview feature actually doesn't  

play02:14

really work on a mac as in it's not running  inside of xcode as it is for an ios app  

play02:20

but when you click the little play button in your  joy preview it will actually uh open a new window  

play02:25

of your app and display that component there  i don't like how that's handled so i might as  

play02:30

well just run the app you know what i mean so i  just get rid of the switch ui preview on the mac  

play02:37

let's also clean up the persistence controller  i will not be using the preview controller  

play02:42

so that can go i will also remove all of this  in-memory stuff and also remove this large  

play02:50

arrow command and then we will only have our ns  persistent container we will load the persistent  

play02:58

stores and we will make sure that automatically  merges changes from parent is set to true  

play03:05

i don't think that this is really important as  we're not dealing with cloud kit i think this  

play03:10

line is only important for cloud kit but we'll  leave it in anyways then let's also clean up the  

play03:15

model so we don't need this random item entity so  let's get rid of that and i think we can just get  

play03:22

started by creating our model so let's add entity  i will call this one ql link for quick links link

play03:33

and it will have an id  

play03:37

a title and a url the url will be a string the id  will be a uid and the title will also be a string

play03:54

if you have watched my previous live  stream where i've built a pros and cons mac  

play03:58

app then you will know that i tend to manually  generate my classes so if i select the entity  

play04:05

and then here in the inspector for the  code gen i just select manual slash none  

play04:12

hide this again and then go to editor  create ns manage object subclass hit next

play04:28

let's create a new photo for that actually

play04:44

and let's save it into this  data folder that i just created  

play04:48

okay for some reason the folder didn't work

play04:53

so let's just create a group let's call it data

play04:59

interesting so let's call it call data  and then move both of these in there  

play05:08

now let's hit command b to build  the project and see if it works

play05:18

okay build succeeded these are just two xcode  arrows the first time that you build a core  

play05:22

data project this will pretty much always  happen so let's just press command shift k  

play05:29

okay and then in here let's create some wrapper  attributes so we don't always have to unwrap the  

play05:36

properties so let's create a var wrapped id which  will be of type uid in this case we will just  

play05:44

force unwrap our id so we have we'll have to make  sure that every time that we create a new qr link  

play05:50

we provide a valid uid then let's also create a  wrapped title which will be a string and that's  

play05:57

just our title or an empty string if there is none  and then also a wrapped url which will be a url  

play06:08

and in here we will just construct  a new url via a string of our url

play06:18

or actually

play06:23

url and we'll force unwrap both of these  

play06:28

because in this app we will make sure that  every time that the user creates a new qr link  

play06:33

that they set a id or that we set an id that they  set a title and a url so force unwrapping here  

play06:40

should not be an issue because we make sure that  every time that one of these instances get created  

play06:46

there will be a title and a url so in fact we can  just get rid of this new core listing operator  

play06:51

here as well and just force unwrap the title as  i said might not want to do this in production  

play06:58

but i think for an app where we really strictly  control the input it is fine to do it like this

play07:05

okay so the call data part is actually  already done next up let's create a new  

play07:11

group here and let's call it menu bar and this  will contain all of the code that we need to

play07:19

add a menu bar item so first of all  we need to create an app delegate

play07:28

that is needed to register the menu bar item so  let's import swift ui and let's create a class  

play07:36

called app delegate which will inherit from ns  object and conform to ns application delegate

play07:48

this class will need a pop  over that that is basically  

play07:57

what's shown when the user clicks on the  menu bar item so this thing here is called  

play08:00

an ns pop over and this will be an ns  pop over and now this is a bit ugly  

play08:11

but i will make this object static because we  will need to access it in some other places  

play08:17

so yeah i'll make it static but maybe you  shouldn't do that i'm not too sure i'm just  

play08:24

gonna go ahead and do it and leave you with the  information that it's probably not the best idea

play08:30

okay and next let's application did finish  launching is the delegate function that we  

play08:38

need here and here we can set our self dot  pop over we need that capital s self because  

play08:46

the pop over is a static variable to access  the static variable we need to access it via  

play08:51

the class we could also say appdelegate.popover  i like to say self.popover so it's instantly  

play08:58

recognizable that this is actually in the  same class it's just a static variable  

play09:03

and then here we can set the content  viewcontroller to be an ns hosting controller

play09:10

with a root view and now we don't have  that view yet so let's create that  

play09:17

so let's create a new file switch your eye  view and i'll just call it pop overview

play09:27

we can leave that as is for now and  then in our app delegate here we can say  

play09:32

the root view is a pop overview  but as we're working with call data  

play09:38

we will have to pass the manage object context  into the search ui environment so to do that  

play09:44

we will say dot environment access dot managed  object context and the context will be our shared  

play09:50

persistence controllers context so we will say  persistencecontroller.shared.container.viewcontext

play10:02

okay to make sure that the pop over gets  hidden again when the user clicks somewhere  

play10:08

else on their device we will have to set  the behavior of the pop over to be transient

play10:18

okay next up let's create a status  bar controller that's what i called it  

play10:23

and we will use that to fill in this sf symbol  here and to tell it what to do once the user  

play10:29

clicks on it so let's create a new file  and let's call it status bar controller

play10:38

okay so let's import app kit let's create a class  called status bar controller this will have a  

play10:49

private var status bar which is an ns status  bar so status bar of type n as status bar  

play10:58

and then two more attributes and and a status item  

play11:02

and then ns popover which will then  present our circular view so let's add

play11:11

a status item as well and a status item  and then let's also add our pop over

play11:26

just like that okay let's create an initializer  where we pass in a popover which we then  

play11:32

configure in the status bar controller so  this will be of type ns pop over first thing  

play11:38

that we do is make sure that we save it into our  property here so self.popover equals that popover  

play11:48

we will also initialize the status bar so status  bar equals dot it and then status item is our  

play12:00

status bar and then we can create a new status  item from that and a status bar with a length of  

play12:09

ns status item dot variable length you can  also specify how large your status item will be  

play12:19

but i think it's just a bit easier to work with it  if you just select any status item.variable length  

play12:25

i think that's the easiest solution and then  next let's configure the button give it an action  

play12:32

give it a target and give it an  ns image to show in the menu bar  

play12:37

so let's grab the button first of all so  let's say if let button equals our status item  

play12:45

dot button this is a optional property so we have  to do all this if that stuff and then in here we  

play12:51

can configure so we can say button.image  is an ns image with a system symbol name  

play12:59

and you can use any system symbol that you  want i will just use house right now the one  

play13:06

that i've showed you earlier is list dot bullet  dot rectangle dot fill but you can use any as  

play13:13

of symbol that you'd like and as this is just  an example project let's set the accessibility  

play13:19

description to nil if you were to create an app  like this and then push it to the app store you  

play13:24

should probably care about accessibility and add  a description right there next let's add an action  

play13:33

and that is a selector and we'll have to create  a function for that so let's create an add  

play13:40

objective c func show our app that's what we want  to happen when the user clicks on um the ns image  

play13:54

so it will have a sender of any object  that's just the signature that we need

play14:00

okay

play14:04

and now we can use that here in our  selector and let's also make sure that the  

play14:10

target is set to self so this selector is called  on our own instance next let's implement this  

play14:21

show app function so there will be two cases  either the pop over is shown then we want to  

play14:29

hide our switcher eye view and else we  want to show us with your view so hiding  

play14:34

is actually super easy let's just say popover  dot perform close with no sender we don't care  

play14:41

about that just close the pop over and else if  we want to show it we can just say popover dot  

play14:48

show relative to the bounds  of our status item button  

play14:53

so let's say status item dot button we can false  unwrap this because we have created a button here

play15:03

dot bound

play15:07

of our status item dot button once again  the preferred edge is the maximum y edge so  

play15:17

the top basically okay and that's already  everything in our status bar controller  

play15:22

let's go back into our app delegate and in here  we now need to create a status bar controller  

play15:31

so let's add a variable for that let's call it  status bar and that will be of type status bar  

play15:38

controller optional and then we can say status bar  is a new status bar controller with our pop over

play15:51

self.pop over of course all right  so now all of the menu bar stuff  

play15:57

is almost set up the last thing that we need  to do is register this app delegate in our app  

play16:04

so to do that let's open our menu by links app  let's go in here and let's say add ns application

play16:16

delegate adapter with the type of appdelegator  itself is our new app delegate this way it's  

play16:24

registered in the app and the system will  actually call all of these application did  

play16:29

finish launching functions and so on okay we  can close the app delegate we can close the app  

play16:36

and i think now we can give it a run and  perhaps already see something in the menu bar  

play16:42

okay so now the app is running we have our window  with the welcome to menu bar links content view  

play16:49

and then we also have this little  house icon here in the menu bar  

play16:52

and if we click that then a pop over will be  shown it's not well aligned and it looks very  

play17:00

bad because there is no padding but we will fix  all of that okay so let's close the app again

play17:10

let's also clean up the sidebar here a bit  so let's move the core data model and the  

play17:15

persistence controller into the core data group  let's collapse both of these let's move the pop  

play17:22

overview up a bit and i think that's already  the next thing that we can have a look at so

play17:31

let's open so let's open the pop overview next  

play17:36

and in here we now create basically  everything that's in this app here  

play17:42

so for that first of all since we're dealing  with core data we need a managed object context  

play17:49

and then we can also create a fetch request for  our ql links so let's create an add fetch request

play17:58

with empty sort descriptors we don't really care  about sorting them if you care about sorting them  

play18:04

this is the place where you should do it and then  let's call this variable links and this will be a  

play18:10

fetched results of ql link that's what we call  our core data class quick links link basically

play18:19

and then inside of the view let's create a vstack

play18:26

with an alignment of leading  

play18:29

we don't care about the spacing for now in  here let's iterate over all of these links

play18:39

with the wrapped id as our identifier  

play18:43

and let's grab a link in here for each link let  me remind you we basically have an h stack with a  

play18:51

clickable text link and then a spacer and a button  on the right hand side which copies the link  

play18:57

to the paste board so let's get started by  creating an age stack first element will be a link  

play19:05

with a title of our wrap title and  a destination of our wrapped url

play19:14

then we said we want to have a spacer

play19:18

and next there will be a button

play19:22

with a label that is an sf symbol so system  name and this one has a bit of a longer name  

play19:30

the one that i used so let's use the same  one again it's called arrow right on right  

play19:37

doc on clipboard i think that's the correct name  next up let's do that and it's pastebot management  

play19:47

so you'd think that it's super easy and i will  show you how the basic api to copy something  

play19:52

into the pasteboard works so you can just say ns  pasteboard dot general dot set string or set any  

play20:01

other type that you want we will use the set  string we will set the link dot wrapped url

play20:12

and as the type we will of course use  string because we are copying a string  

play20:20

and actually we're not using  the wrapped url we're using the  

play20:23

your l and then force unwrap it because  we want a string and not a url object  

play20:28

don't mind this xcode internal error i don't  know why but ever since xcode 13.3 came around  

play20:34

it constantly crashes for me now as i said you  might expect this to be super easy but only  

play20:42

using this line of code and setting a string  will actually do nothing to the end spaceboard  

play20:47

you have to clear its contents first to do that  let's say ns pasteboard.general.clearcontents

play20:56

and now the set string function actually does  something i have no idea why this api is in  

play21:01

place i think it would be a lot easier to use  if you don't have to call clear contents first  

play21:07

but that's just the way it  is and then we also want to  

play21:11

close the pop over again so let me  demonstrate that in the completed app  

play21:17

when i click the button the pop over closes  we will also want to have that behavior  

play21:21

and to achieve that we can call our appdelegate  dot pop over and this is why we made it a static  

play21:28

property so we can access it right here and we  can then just call perform close with no sender

play21:38

okay so that's basically the top portion done  and let's do the bottom half so as you can see  

play21:43

there is a divider and then two text fields and  two buttons so let's collapse this for each so  

play21:49

we have a bit more space let's create a divider  for the divider let's also give it some vertical  

play21:56

padding of maybe four points and below that let's  create a new v-stack so everything is yeah in its  

play22:05

own component basically you could extract that to  a different view i'll just keep it in a stake here  

play22:12

it's not really needed because you already have  a v stack surrounding it i just like to separate  

play22:17

my components a bit indicates that i want to  pull it out into its own swift ui view later on

play22:26

so in this vstack as we already  discussed there will be two text fields  

play22:30

the first one will be the title of our new  link and the second one will then be the url

play22:39

so let's create two new state properties the  first one will be link title that's a string and  

play22:46

of course it will be empty by default let's  just create a second one called link url  

play22:52

and let's bind to these so first one is  link title the second one is link url

play23:01

and next we will add the quit button and the  save button for that let's create an h stack  

play23:09

first button will just say quit and this one  will call terminate on the ns application so ns  

play23:18

application dot shared o ns application dot  shared dot terminate with no sender again  

play23:30

then a spacer in between and  a second button called save

play23:38

now i want this button to be highlighted so the  user knows that this is the primary button so we  

play23:43

will just add a bolded prominent button style  this will then also give it a tint color that  

play23:49

the user selected in their mac os settings okay  and in here let's make sure that both of the text  

play23:56

fields are filled out correctly so let's say so  let's make sure that there's a valid url so if url  

play24:03

with the string of our link url is not  new and the link title is not empty  

play24:13

is not empty and the link url also is not empty  i'm not sure if an empty url is actually valid so  

play24:22

i'll just double check it here make sure that is  a valid url and then the string is also not empty  

play24:29

if all of that is the case then  we can create a new ql link  

play24:36

that will be a qr link with our current  view context for that qr link we will  

play24:44

set the id to be a brand new uuid we  will set the title to be our link title

play24:53

and we will set the url to be our link url

play25:00

then let's save the changes so try view context  and save and let's also clear out both of the  

play25:07

text fields in case you want to add multiple  links at once and of course there is a typo here

play25:17

okay so let's say link title is an empty  string and link url is also an empty string

play25:28

okay just a few more things so let's run  this right now see how it looks and then  

play25:33

see what we want to change okay so  still the main app window will open  

play25:39

with our content view we have our menu bar  item and that's what it looks like right now so  

play25:46

of course we don't have any links yet but we  have a text field another text field and our  

play25:52

two buttons but they're really crammed in there  so let's give it some padding and a frame as well

play26:00

so on our v stack closing brackets let's add some  padding and let's give it a fixed width of 200

play26:14

okay let's run that again

play26:19

and now it already looks a lot better of  course there are still no links because  

play26:22

we haven't entered any so let's enter  a link and for that i will actually  

play26:27

use the completed app so i  will just copy my twitter url  

play26:31

let's call this one twitter and as url let me  paste what i just copied there you can see that  

play26:38

it definitely works when i hit save then it  will be entered here okay so one more thing  

play26:43

you might want to remove the button style for this  button so let's do that first expand our for each  

play26:50

again and then for the second button let's add a  button style of plane that should get rid of it

play26:58

okay there's basically one last thing  and if i run the app again i can show you  

play27:04

why it's needed or why it might be nice so  our app really only lives in a menu bar the  

play27:09

main window serves no purpose at all everything  happens in this little menu bar pop over so why  

play27:18

don't we just get rid of this window altogether  to do that let's go into the application project  

play27:28

select the target go to info.pdist  and add a new value and scroll down

play27:36

until you can see application is agent  and then you can just set that to yes  

play27:44

now if we hit save and run the app again

play27:50

the window will open because we had it  open last time but if we close it now  

play27:54

and run the app again then it will not open again  so what this means is the first time that the user  

play28:00

opened opens the app for example you could show  a little onboarding window and then the next  

play28:06

times that the app will be open no window will be  shown and you will just have your menu bar item  

play28:12

okay that's it with the video i hope you  learned something i hope you found it enjoyable  

play28:17

i think this app is rather useful  you could of course add more features  

play28:22

to it and also save different kinds of data this  is just what i needed a few days ago so i built it  

play28:28

if you enjoyed the video please make  sure to subscribe to the channel  

play28:32

like the video and leave a comment down below  with other app ideas that we should build in  

play28:35

this more relaxed color long format also  of course tutorial ideas if you have any

play28:47

you

Rate This

5.0 / 5 (0 votes)

Related Tags
Mac OS菜单栏应用用户交互剪贴板软件开发Swift UI教程编程应用开发技术分享
Do you need a summary in English?