The extension runs on the iPhone and communicates with the watch:
App/BUCK
apple_binary( name = "ExampleWatchAppExtensionBinary", srcs = glob([ "WatchExtension/**/*.swift", ]), # Without specifying the target, buck will provide a wrong one, # which will cause compiler error. swift_compiler_flags = ["-target", "i386-apple-watchos4.0-simulator"], configs = watch_binary_configs("ExampleApp.WatchApp.Extension"), linker_flags = ["-e", "_WKExtensionMain"], frameworks = [ "$SDKROOT/System/Library/Frameworks/CoreGraphics.framework", "$SDKROOT/System/Library/Frameworks/Foundation.framework", "$SDKROOT/System/Library/Frameworks/UIKit.framework", "$SDKROOT/System/Library/Frameworks/WatchConnectivity.framework", "$SDKROOT/System/Library/Frameworks/WatchKit.framework", ], headers = glob([ "WatchExtension/**/*.h", ]),)
The linker flag -e _WKExtensionMain tells the linker that this is a WatchKit extension with a custom entry point instead of the standard main function.
import WatchKitimport Foundationclass ExtensionDelegate: NSObject, WKExtensionDelegate { func applicationDidFinishLaunching() { print("Yo, started with BUCK!") } func applicationDidBecomeActive() { // Restart any tasks that were paused } func applicationWillResignActive() { // Pause ongoing tasks } func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) { for task in backgroundTasks { switch task { case let backgroundTask as WKApplicationRefreshBackgroundTask: backgroundTask.setTaskCompletedWithSnapshot(false) case let snapshotTask as WKSnapshotRefreshBackgroundTask: snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil) case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask: connectivityTask.setTaskCompletedWithSnapshot(false) case let urlSessionTask as WKURLSessionRefreshBackgroundTask: urlSessionTask.setTaskCompletedWithSnapshot(false) default: task.setTaskCompletedWithSnapshot(false) } } }}
The watch app must be defined in the same BUCK file as the main app binary.Xcode is finicky about watch app embedding. Watch apps are built into the watchos build directory, but Xcode only knows to look there if the watch target is defined in the same pbxproj as the main app. If the SDKROOT = watchos; setting is in a different pbxproj, the Copy Files phase will search the iphoneos directory and fail.
apple_binary( name = "ExampleMessageExtensionBinary", srcs = glob([ "MessageExtension/**/*.swift", ]), configs = message_binary_configs("ExampleApp.MessageExtension"), # "-e _NSExtensionMain" tells linker this binary is app extension, # so it won't fail due to missing _main # "-Xlinker -rpath -Xlinker @executable_path/../../Frameworks" tells the # executable binary to look for frameworks in ExampleApp.app/Frameworks # instead of PlugIns, so that we don't need to have the libSwift*.dylib # in ExampleApp.app/PlugIns/*.appex/Frameworks linker_flags = [ "-e", "_NSExtensionMain", "-Xlinker", "-rpath", "-Xlinker", "@executable_path/../../Frameworks", ],)
Understanding the linker flags
-e _NSExtensionMain: Specifies the custom entry point for app extensions instead of the standard main function
-rpath @executable_path/../../Frameworks: Tells the extension to find Swift runtime libraries in the main app’s Frameworks folder rather than duplicating them in the extension, reducing app size
import UIKitimport Messagesclass MessagesViewController: MSMessagesAppViewController { override func viewDidLoad() { super.viewDidLoad() } // MARK: - Conversation Handling override func willBecomeActive(with conversation: MSConversation) { // Called when the extension is about to present UI } override func didResignActive(with conversation: MSConversation) { // Called when the user dismisses the extension } override func didReceive(_ message: MSMessage, conversation: MSConversation) { // Called when a message arrives from another instance } override func didStartSending(_ message: MSMessage, conversation: MSConversation) { // Called when the user taps the send button } override func didCancelSending(_ message: MSMessage, conversation: MSConversation) { // Called when the user deletes the message }}