├── RemotePhone
├── Assets.xcassets
│ ├── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── RemotePhoneApp.swift
├── Info.plist
└── ContentView.swift
├── RemoteTV
├── Assets.xcassets
│ ├── Contents.json
│ ├── App Icon & Top Shelf Image.brandassets
│ │ ├── App Icon.imagestack
│ │ │ ├── Back.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Front.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Middle.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── App Icon - App Store.imagestack
│ │ │ ├── Back.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Front.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Middle.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── Top Shelf Image.imageset
│ │ │ └── Contents.json
│ │ ├── Top Shelf Image Wide.imageset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ └── AccentColor.colorset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── RemoteApp.swift
├── Info.plist
└── ContentView.swift
├── Remote.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcuserdata
│ └── bendodson.xcuserdatad
│ │ ├── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
├── xcshareddata
│ └── xcschemes
│ │ └── RemoteTV.xcscheme
└── project.pbxproj
├── LocalDeviceManager.swift
└── README.md
/RemotePhone/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/RemotePhone/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/RemoteTV/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Remote.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/RemotePhone/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Remote.xcodeproj/xcuserdata/bendodson.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/RemoteTV/RemoteApp.swift:
--------------------------------------------------------------------------------
1 | // Developed by Ben Dodson (ben@bendodson.com)
2 |
3 | import SwiftUI
4 |
5 | @main
6 | struct RemoteApp: App {
7 | var body: some Scene {
8 | WindowGroup {
9 | ContentView()
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/RemotePhone/RemotePhoneApp.swift:
--------------------------------------------------------------------------------
1 | // Developed by Ben Dodson (ben@bendodson.com)
2 |
3 | import SwiftUI
4 |
5 | @main
6 | struct RemotePhoneApp: App {
7 | var body: some Scene {
8 | WindowGroup {
9 | ContentView()
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/RemotePhone/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Remote.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "layers" : [
7 | {
8 | "filename" : "Front.imagestacklayer"
9 | },
10 | {
11 | "filename" : "Middle.imagestacklayer"
12 | },
13 | {
14 | "filename" : "Back.imagestacklayer"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "layers" : [
7 | {
8 | "filename" : "Front.imagestacklayer"
9 | },
10 | {
11 | "filename" : "Middle.imagestacklayer"
12 | },
13 | {
14 | "filename" : "Back.imagestacklayer"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/RemotePhone/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSApplicationServices
6 |
7 | Advertises
8 |
9 |
10 | NSApplicationServiceIdentifier
11 | remote
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/RemoteTV/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSApplicationServices
6 |
7 | Browses
8 |
9 |
10 | NSApplicationServiceIdentifier
11 | remote
12 | NSApplicationServicePlatformSupport
13 |
14 | iOS
15 |
16 | NSApplicationServiceUsageDescription
17 | This app can be controlled from iOS
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Remote.xcodeproj/xcuserdata/bendodson.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | RemotePhone.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 1
11 |
12 | RemoteTV.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 0
16 |
17 |
18 | SuppressBuildableAutocreation
19 |
20 | 2BE664852A09130700E83244
21 |
22 | primary
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/RemoteTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "assets" : [
3 | {
4 | "filename" : "App Icon - App Store.imagestack",
5 | "idiom" : "tv",
6 | "role" : "primary-app-icon",
7 | "size" : "1280x768"
8 | },
9 | {
10 | "filename" : "App Icon.imagestack",
11 | "idiom" : "tv",
12 | "role" : "primary-app-icon",
13 | "size" : "400x240"
14 | },
15 | {
16 | "filename" : "Top Shelf Image Wide.imageset",
17 | "idiom" : "tv",
18 | "role" : "top-shelf-image-wide",
19 | "size" : "2320x720"
20 | },
21 | {
22 | "filename" : "Top Shelf Image.imageset",
23 | "idiom" : "tv",
24 | "role" : "top-shelf-image",
25 | "size" : "1920x720"
26 | }
27 | ],
28 | "info" : {
29 | "author" : "xcode",
30 | "version" : 1
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/RemotePhone/ContentView.swift:
--------------------------------------------------------------------------------
1 | // Developed by Ben Dodson (ben@bendodson.com)
2 |
3 | import SwiftUI
4 |
5 | struct ContentView: View {
6 |
7 | @ObservedObject private var deviceManager = LocalDeviceManager(applicationService: "remote", didReceiveMessage: { data in
8 | guard let string = String(data: data, encoding: .utf8) else { return }
9 | NSLog("Message: \(string)")
10 | }, errorHandler: { error in
11 | NSLog("ERROR: \(error)")
12 | })
13 |
14 | var body: some View {
15 | VStack {
16 | if deviceManager.isConnected {
17 | Text("Connected!")
18 | Button {
19 | deviceManager.send("Hello from iOS!")
20 | } label: {
21 | Text("Send")
22 | }
23 | Button {
24 | deviceManager.disconnect()
25 | } label: {
26 | Text("Disconnect")
27 | }
28 |
29 | } else {
30 | Text("Not Connected")
31 | }
32 | }
33 | .padding()
34 | .onAppear {
35 | try? deviceManager.createListener()
36 | }
37 | }
38 | }
39 |
40 | struct ContentView_Previews: PreviewProvider {
41 | static var previews: some View {
42 | ContentView()
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/RemoteTV/ContentView.swift:
--------------------------------------------------------------------------------
1 | // Developed by Ben Dodson (ben@bendodson.com)
2 |
3 | import SwiftUI
4 | import DeviceDiscoveryUI
5 |
6 | struct ContentView: View {
7 |
8 |
9 |
10 | @ObservedObject private var deviceManager = LocalDeviceManager(applicationService: "remote", didReceiveMessage: { data in
11 | guard let string = String(data: data, encoding: .utf8) else { return }
12 | NSLog("Message: \(string)")
13 | }, errorHandler: { error in
14 | NSLog("ERROR: \(error)")
15 | })
16 |
17 | @State private var showDevicePicker = false
18 |
19 | var body: some View {
20 | VStack {
21 |
22 | if deviceManager.isConnected {
23 | Button("Send") {
24 | deviceManager.send("Hello from tvOS!")
25 | }
26 |
27 | Button("Disconnect") {
28 | deviceManager.disconnect()
29 | }
30 | } else {
31 | DevicePicker(.applicationService(name: "remote")) { endpoint in
32 | deviceManager.connect(to: endpoint)
33 | } label: {
34 | Text("Connect to a local device.")
35 | } fallback: {
36 | Text("Device browsing is not supported on this device")
37 | } parameters: {
38 | .applicationService
39 | }
40 | }
41 |
42 | }
43 | .padding()
44 |
45 | }
46 | }
47 |
48 | struct ContentView_Previews: PreviewProvider {
49 | static var previews: some View {
50 | ContentView()
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Remote.xcodeproj/xcshareddata/xcschemes/RemoteTV.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
42 |
44 |
50 |
51 |
52 |
53 |
59 |
61 |
67 |
68 |
69 |
70 |
72 |
73 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/LocalDeviceManager.swift:
--------------------------------------------------------------------------------
1 | // Developed by Ben Dodson (ben@bendodson.com)
2 |
3 | import SwiftUI
4 | import Network
5 |
6 | class LocalDeviceManager: ObservableObject {
7 |
8 | public private(set) var applicationService: String
9 | public var didReceiveMessage: ((Data) -> Void)?
10 | public var errorHandler: ((Error) -> Void)?
11 | public private(set) var minimumIncompleteLength: Int
12 | public private(set) var maximumLength: Int
13 |
14 | private var listener: NWListener?
15 | private var endpoint: NWEndpoint?
16 | private var connection: NWConnection?
17 |
18 | init(applicationService: String, didReceiveMessage: ( (Data) -> Void)? = nil, errorHandler: ( (Error) -> Void)? = nil, minimumIncompleteLength: Int = 1024, maximumLength: Int = 1024 * 512) {
19 | self.applicationService = applicationService
20 | self.didReceiveMessage = didReceiveMessage
21 | self.errorHandler = errorHandler
22 | self.minimumIncompleteLength = minimumIncompleteLength
23 | self.maximumLength = maximumLength
24 | }
25 |
26 | var isConnected: Bool {
27 | guard let connection else { return false }
28 | return connection.state == .ready
29 | }
30 |
31 | func connect(to endpoint: NWEndpoint) {
32 | self.endpoint = endpoint
33 | let connection = NWConnection(to: endpoint, using: .applicationService)
34 | setUpConnection(connection)
35 | }
36 |
37 | func disconnect() {
38 | connection?.cancel()
39 | connection = nil
40 | }
41 |
42 | func createListener() throws {
43 | listener = try NWListener(using: .applicationService)
44 | listener?.service = .init(applicationService: applicationService)
45 |
46 | listener?.stateUpdateHandler = { [weak self] state in
47 | guard let self else { return }
48 | switch state {
49 | case .failed(let error):
50 | errorHandler?(error)
51 | disconnect()
52 | default:
53 | break
54 | }
55 | self.objectWillChange.send()
56 | }
57 |
58 | listener?.newConnectionHandler = { connection in
59 | self.setUpConnection(connection)
60 | }
61 |
62 | listener?.start(queue: .main)
63 | }
64 |
65 | func send(_ string: String) {
66 | guard let data = string.data(using: .utf8) else { return }
67 | connection?.send(content: data, completion: .contentProcessed({ [weak self] error in
68 | guard let self else { return }
69 | if let error {
70 | errorHandler?(error)
71 | }
72 | }))
73 | }
74 |
75 | private func setUpConnection(_ connection: NWConnection) {
76 | self.connection = connection
77 |
78 | connection.stateUpdateHandler = { [weak self] state in
79 | guard let self else { return }
80 | switch state {
81 | case .failed(let error):
82 | errorHandler?(error)
83 | disconnect()
84 | default:
85 | break
86 | }
87 | self.objectWillChange.send()
88 | }
89 |
90 | receive()
91 | connection.start(queue: .main)
92 | }
93 |
94 | private func receive() {
95 | guard let connection else { return }
96 | connection.receive(minimumIncompleteLength: 1, maximumLength: 1024 * 1024) { [weak self] content, contentContext, isComplete, error in
97 | guard let self else { return }
98 | if let error {
99 | errorHandler?(error)
100 | }
101 | if let content {
102 | didReceiveMessage?(content)
103 | }
104 | receive()
105 | }
106 | }
107 |
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LocalDeviceManager
2 | Using `DeviceDiscoveryUI` to connect an Apple TV app to an iPhone, iPad, or Apple Watch.
3 |
4 | ## Limitations of DeviceDiscoveryUI
5 | - Only runs on Apple TV 4K currently (Apple TV HD is not supported)
6 | - The tvOS app can only connect to one device at a time (i.e. you couldn’t make a game with this that used two iPhones as controllers)
7 | - The tvOS app can only connect to other versions of your app that share the same bundle identifier (and are thus sold with [Universal Purchase](https://developer.apple.com/support/universal-purchase/))
8 | - This will not work on either the tvOS or iOS simulators. You must use physical devices.
9 |
10 | ## Usage
11 | There are a few steps you need to run through in order to communicate between Apple TV and another device (I'll use an iPhone for all examples but the same code would apply to iPad or Apple Watch).
12 |
13 | ### Apple TV
14 |
15 | #### Step 1.
16 | Define supported devices with an [NSApplicationServices key](https://developer.apple.com/documentation/devicediscoveryui/connecting_a_tvos_app_to_other_devices_over_the_local_network#3976721) in your Info.plist
17 |
18 | #### Step 2.
19 | Instantiate the `LocalDeviceManager` using the application service key you defined in Step 1 (i.e. "remote" in this example):
20 |
21 | ```
22 | @ObservedObject private var deviceManager = LocalDeviceManager(applicationService: "remote", didReceiveMessage: { data in
23 | guard let string = String(data: data, encoding: .utf8) else { return }
24 | NSLog("Message: \(string)")
25 | }, errorHandler: { error in
26 | NSLog("ERROR: \(error)")
27 | })
28 | ```
29 |
30 | The `LocalDeviceManager` has a callback for when messages are received. You have access to the raw `Data` but I would suggest you only send encoded strings rather than custom models in case the packets are delivered in chunks.
31 |
32 | An error handler is also provided in case of failures.
33 |
34 | #### Step 3.
35 | Present the Device Picker UI. This demo uses SwiftUI but you can use [`DDDevicePickerViewController`](https://developer.apple.com/documentation/devicediscoveryui/dddevicepickerviewcontroller) in UIKit.
36 |
37 | ```
38 | @State private var showDevicePicker = false
39 |
40 | var body: some View {
41 | VStack {
42 | if deviceManager.isConnected {
43 | Button("Send") {
44 | deviceManager.send("Hello from tvOS!")
45 | }
46 |
47 | Button("Disconnect") {
48 | deviceManager.disconnect()
49 | }
50 | } else {
51 | DevicePicker(.applicationService(name: "remote")) { endpoint in
52 | deviceManager.connect(to: endpoint)
53 | } label: {
54 | Text("Connect to a local device.")
55 | } fallback: {
56 | Text("Device browsing is not supported on this device")
57 | } parameters: {
58 | .applicationService
59 | }
60 | }
61 | }
62 | .padding()
63 | }
64 | ```
65 |
66 | This will present the native device picker. Upon selecting a device, a notification will be sent asking the user to either download the app or open the app if installed. Once they do this, the connection will be established.
67 |
68 | 
69 |
70 |
71 | ### iPhone / iPad / Apple Watch
72 |
73 | #### Step 1.
74 | Declare the device can listen for connections by using an [NSApplicationServices key](https://developer.apple.com/documentation/devicediscoveryui/connecting_a_tvos_app_to_other_devices_over_the_local_network#3986063) in your Info.plist
75 |
76 | #### Step 2.
77 | Instantiate the `LocalDeviceManager` using the application service key you defined in Step 1 (i.e. "remote" in this example):
78 |
79 | ```
80 | @ObservedObject private var deviceManager = LocalDeviceManager(applicationService: "remote", didReceiveMessage: { data in
81 | guard let string = String(data: data, encoding: .utf8) else { return }
82 | NSLog("Message: \(string)")
83 | }, errorHandler: { error in
84 | NSLog("ERROR: \(error)")
85 | })
86 | ```
87 |
88 | This is identical to the tvOS implementation.
89 |
90 | #### Step 3.
91 | Create your UI and ensure that the `LocalDeviceManager` listener is created as soon as possible.
92 |
93 | ```
94 | var body: some View {
95 | VStack {
96 | if deviceManager.isConnected {
97 | Text("Connected!")
98 | Button {
99 | deviceManager.send("Hello from iOS!")
100 | } label: {
101 | Text("Send")
102 | }
103 | Button {
104 | deviceManager.disconnect()
105 | } label: {
106 | Text("Disconnect")
107 | }
108 | } else {
109 | Text("Not Connected")
110 | }
111 | }
112 | .padding()
113 | .onAppear {
114 | try? deviceManager.createListener()
115 | }
116 | }
117 | ```
118 |
119 | You can now send data from tvOS to your connected device and vice versa.
120 |
121 | ## Find Out More
122 | You can read [my blog post](https://bendodson.com/weblog/2023/05/10/connecting-a-tvos-app-to-ios-ipados-and-watchos-with-devicediscoveryui/) on this topic to learn more about DeviceDiscoveryUI in tvOS 16 and how I'm using this class in my own apps.
123 |
--------------------------------------------------------------------------------
/Remote.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 56;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 2BE6648A2A09130700E83244 /* RemoteApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BE664892A09130700E83244 /* RemoteApp.swift */; };
11 | 2BE6648C2A09130700E83244 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BE6648B2A09130700E83244 /* ContentView.swift */; };
12 | 2BE6648E2A09130800E83244 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2BE6648D2A09130800E83244 /* Assets.xcassets */; };
13 | 2BE664912A09130800E83244 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2BE664902A09130800E83244 /* Preview Assets.xcassets */; };
14 | 2BE664992A09147B00E83244 /* LocalDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BE664982A09147B00E83244 /* LocalDeviceManager.swift */; };
15 | 2BE664A12A0917F300E83244 /* RemotePhoneApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BE664A02A0917F300E83244 /* RemotePhoneApp.swift */; };
16 | 2BE664A32A0917F300E83244 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BE664A22A0917F300E83244 /* ContentView.swift */; };
17 | 2BE664A52A0917F500E83244 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2BE664A42A0917F500E83244 /* Assets.xcassets */; };
18 | 2BE664A82A0917F500E83244 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2BE664A72A0917F500E83244 /* Preview Assets.xcassets */; };
19 | 2BE664AC2A09182600E83244 /* LocalDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BE664982A09147B00E83244 /* LocalDeviceManager.swift */; };
20 | /* End PBXBuildFile section */
21 |
22 | /* Begin PBXFileReference section */
23 | 2BAB82F02A091ACC0038B2DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; };
24 | 2BE664862A09130700E83244 /* RemoteTV.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RemoteTV.app; sourceTree = BUILT_PRODUCTS_DIR; };
25 | 2BE664892A09130700E83244 /* RemoteApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteApp.swift; sourceTree = ""; };
26 | 2BE6648B2A09130700E83244 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
27 | 2BE6648D2A09130800E83244 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
28 | 2BE664902A09130800E83244 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
29 | 2BE664972A09133500E83244 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; };
30 | 2BE664982A09147B00E83244 /* LocalDeviceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalDeviceManager.swift; sourceTree = ""; };
31 | 2BE6649E2A0917F300E83244 /* RemotePhone.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RemotePhone.app; sourceTree = BUILT_PRODUCTS_DIR; };
32 | 2BE664A02A0917F300E83244 /* RemotePhoneApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemotePhoneApp.swift; sourceTree = ""; };
33 | 2BE664A22A0917F300E83244 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
34 | 2BE664A42A0917F500E83244 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
35 | 2BE664A72A0917F500E83244 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
36 | /* End PBXFileReference section */
37 |
38 | /* Begin PBXFrameworksBuildPhase section */
39 | 2BE664832A09130700E83244 /* Frameworks */ = {
40 | isa = PBXFrameworksBuildPhase;
41 | buildActionMask = 2147483647;
42 | files = (
43 | );
44 | runOnlyForDeploymentPostprocessing = 0;
45 | };
46 | 2BE6649B2A0917F300E83244 /* Frameworks */ = {
47 | isa = PBXFrameworksBuildPhase;
48 | buildActionMask = 2147483647;
49 | files = (
50 | );
51 | runOnlyForDeploymentPostprocessing = 0;
52 | };
53 | /* End PBXFrameworksBuildPhase section */
54 |
55 | /* Begin PBXGroup section */
56 | 2BE6647D2A09130700E83244 = {
57 | isa = PBXGroup;
58 | children = (
59 | 2BE664982A09147B00E83244 /* LocalDeviceManager.swift */,
60 | 2BE664882A09130700E83244 /* RemoteTV */,
61 | 2BE6649F2A0917F300E83244 /* RemotePhone */,
62 | 2BE664872A09130700E83244 /* Products */,
63 | );
64 | sourceTree = "";
65 | };
66 | 2BE664872A09130700E83244 /* Products */ = {
67 | isa = PBXGroup;
68 | children = (
69 | 2BE664862A09130700E83244 /* RemoteTV.app */,
70 | 2BE6649E2A0917F300E83244 /* RemotePhone.app */,
71 | );
72 | name = Products;
73 | sourceTree = "";
74 | };
75 | 2BE664882A09130700E83244 /* RemoteTV */ = {
76 | isa = PBXGroup;
77 | children = (
78 | 2BE664972A09133500E83244 /* Info.plist */,
79 | 2BE664892A09130700E83244 /* RemoteApp.swift */,
80 | 2BE6648B2A09130700E83244 /* ContentView.swift */,
81 | 2BE6648D2A09130800E83244 /* Assets.xcassets */,
82 | 2BE6648F2A09130800E83244 /* Preview Content */,
83 | );
84 | path = RemoteTV;
85 | sourceTree = "";
86 | };
87 | 2BE6648F2A09130800E83244 /* Preview Content */ = {
88 | isa = PBXGroup;
89 | children = (
90 | 2BE664902A09130800E83244 /* Preview Assets.xcassets */,
91 | );
92 | path = "Preview Content";
93 | sourceTree = "";
94 | };
95 | 2BE6649F2A0917F300E83244 /* RemotePhone */ = {
96 | isa = PBXGroup;
97 | children = (
98 | 2BAB82F02A091ACC0038B2DD /* Info.plist */,
99 | 2BE664A02A0917F300E83244 /* RemotePhoneApp.swift */,
100 | 2BE664A22A0917F300E83244 /* ContentView.swift */,
101 | 2BE664A42A0917F500E83244 /* Assets.xcassets */,
102 | 2BE664A62A0917F500E83244 /* Preview Content */,
103 | );
104 | path = RemotePhone;
105 | sourceTree = "";
106 | };
107 | 2BE664A62A0917F500E83244 /* Preview Content */ = {
108 | isa = PBXGroup;
109 | children = (
110 | 2BE664A72A0917F500E83244 /* Preview Assets.xcassets */,
111 | );
112 | path = "Preview Content";
113 | sourceTree = "";
114 | };
115 | /* End PBXGroup section */
116 |
117 | /* Begin PBXNativeTarget section */
118 | 2BE664852A09130700E83244 /* RemoteTV */ = {
119 | isa = PBXNativeTarget;
120 | buildConfigurationList = 2BE664942A09130800E83244 /* Build configuration list for PBXNativeTarget "RemoteTV" */;
121 | buildPhases = (
122 | 2BE664822A09130700E83244 /* Sources */,
123 | 2BE664832A09130700E83244 /* Frameworks */,
124 | 2BE664842A09130700E83244 /* Resources */,
125 | );
126 | buildRules = (
127 | );
128 | dependencies = (
129 | );
130 | name = RemoteTV;
131 | productName = Remote;
132 | productReference = 2BE664862A09130700E83244 /* RemoteTV.app */;
133 | productType = "com.apple.product-type.application";
134 | };
135 | 2BE6649D2A0917F300E83244 /* RemotePhone */ = {
136 | isa = PBXNativeTarget;
137 | buildConfigurationList = 2BE664A92A0917F500E83244 /* Build configuration list for PBXNativeTarget "RemotePhone" */;
138 | buildPhases = (
139 | 2BE6649A2A0917F300E83244 /* Sources */,
140 | 2BE6649B2A0917F300E83244 /* Frameworks */,
141 | 2BE6649C2A0917F300E83244 /* Resources */,
142 | );
143 | buildRules = (
144 | );
145 | dependencies = (
146 | );
147 | name = RemotePhone;
148 | productName = RemotePhone;
149 | productReference = 2BE6649E2A0917F300E83244 /* RemotePhone.app */;
150 | productType = "com.apple.product-type.application";
151 | };
152 | /* End PBXNativeTarget section */
153 |
154 | /* Begin PBXProject section */
155 | 2BE6647E2A09130700E83244 /* Project object */ = {
156 | isa = PBXProject;
157 | attributes = {
158 | BuildIndependentTargetsInParallel = 1;
159 | LastSwiftUpdateCheck = 1430;
160 | LastUpgradeCheck = 1430;
161 | TargetAttributes = {
162 | 2BE664852A09130700E83244 = {
163 | CreatedOnToolsVersion = 14.3;
164 | };
165 | 2BE6649D2A0917F300E83244 = {
166 | CreatedOnToolsVersion = 14.3;
167 | };
168 | };
169 | };
170 | buildConfigurationList = 2BE664812A09130700E83244 /* Build configuration list for PBXProject "Remote" */;
171 | compatibilityVersion = "Xcode 14.0";
172 | developmentRegion = en;
173 | hasScannedForEncodings = 0;
174 | knownRegions = (
175 | en,
176 | Base,
177 | );
178 | mainGroup = 2BE6647D2A09130700E83244;
179 | productRefGroup = 2BE664872A09130700E83244 /* Products */;
180 | projectDirPath = "";
181 | projectRoot = "";
182 | targets = (
183 | 2BE664852A09130700E83244 /* RemoteTV */,
184 | 2BE6649D2A0917F300E83244 /* RemotePhone */,
185 | );
186 | };
187 | /* End PBXProject section */
188 |
189 | /* Begin PBXResourcesBuildPhase section */
190 | 2BE664842A09130700E83244 /* Resources */ = {
191 | isa = PBXResourcesBuildPhase;
192 | buildActionMask = 2147483647;
193 | files = (
194 | 2BE664912A09130800E83244 /* Preview Assets.xcassets in Resources */,
195 | 2BE6648E2A09130800E83244 /* Assets.xcassets in Resources */,
196 | );
197 | runOnlyForDeploymentPostprocessing = 0;
198 | };
199 | 2BE6649C2A0917F300E83244 /* Resources */ = {
200 | isa = PBXResourcesBuildPhase;
201 | buildActionMask = 2147483647;
202 | files = (
203 | 2BE664A82A0917F500E83244 /* Preview Assets.xcassets in Resources */,
204 | 2BE664A52A0917F500E83244 /* Assets.xcassets in Resources */,
205 | );
206 | runOnlyForDeploymentPostprocessing = 0;
207 | };
208 | /* End PBXResourcesBuildPhase section */
209 |
210 | /* Begin PBXSourcesBuildPhase section */
211 | 2BE664822A09130700E83244 /* Sources */ = {
212 | isa = PBXSourcesBuildPhase;
213 | buildActionMask = 2147483647;
214 | files = (
215 | 2BE6648C2A09130700E83244 /* ContentView.swift in Sources */,
216 | 2BE6648A2A09130700E83244 /* RemoteApp.swift in Sources */,
217 | 2BE664992A09147B00E83244 /* LocalDeviceManager.swift in Sources */,
218 | );
219 | runOnlyForDeploymentPostprocessing = 0;
220 | };
221 | 2BE6649A2A0917F300E83244 /* Sources */ = {
222 | isa = PBXSourcesBuildPhase;
223 | buildActionMask = 2147483647;
224 | files = (
225 | 2BE664A32A0917F300E83244 /* ContentView.swift in Sources */,
226 | 2BE664A12A0917F300E83244 /* RemotePhoneApp.swift in Sources */,
227 | 2BE664AC2A09182600E83244 /* LocalDeviceManager.swift in Sources */,
228 | );
229 | runOnlyForDeploymentPostprocessing = 0;
230 | };
231 | /* End PBXSourcesBuildPhase section */
232 |
233 | /* Begin XCBuildConfiguration section */
234 | 2BE664922A09130800E83244 /* Debug */ = {
235 | isa = XCBuildConfiguration;
236 | buildSettings = {
237 | ALWAYS_SEARCH_USER_PATHS = NO;
238 | CLANG_ANALYZER_NONNULL = YES;
239 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
240 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
241 | CLANG_ENABLE_MODULES = YES;
242 | CLANG_ENABLE_OBJC_ARC = YES;
243 | CLANG_ENABLE_OBJC_WEAK = YES;
244 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
245 | CLANG_WARN_BOOL_CONVERSION = YES;
246 | CLANG_WARN_COMMA = YES;
247 | CLANG_WARN_CONSTANT_CONVERSION = YES;
248 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
249 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
250 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
251 | CLANG_WARN_EMPTY_BODY = YES;
252 | CLANG_WARN_ENUM_CONVERSION = YES;
253 | CLANG_WARN_INFINITE_RECURSION = YES;
254 | CLANG_WARN_INT_CONVERSION = YES;
255 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
256 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
257 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
258 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
259 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
260 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
261 | CLANG_WARN_STRICT_PROTOTYPES = YES;
262 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
263 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
264 | CLANG_WARN_UNREACHABLE_CODE = YES;
265 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
266 | COPY_PHASE_STRIP = NO;
267 | DEBUG_INFORMATION_FORMAT = dwarf;
268 | ENABLE_STRICT_OBJC_MSGSEND = YES;
269 | ENABLE_TESTABILITY = YES;
270 | GCC_C_LANGUAGE_STANDARD = gnu11;
271 | GCC_DYNAMIC_NO_PIC = NO;
272 | GCC_NO_COMMON_BLOCKS = YES;
273 | GCC_OPTIMIZATION_LEVEL = 0;
274 | GCC_PREPROCESSOR_DEFINITIONS = (
275 | "DEBUG=1",
276 | "$(inherited)",
277 | );
278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
280 | GCC_WARN_UNDECLARED_SELECTOR = YES;
281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
282 | GCC_WARN_UNUSED_FUNCTION = YES;
283 | GCC_WARN_UNUSED_VARIABLE = YES;
284 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
285 | MTL_FAST_MATH = YES;
286 | ONLY_ACTIVE_ARCH = YES;
287 | SDKROOT = appletvos;
288 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
289 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
290 | TVOS_DEPLOYMENT_TARGET = 16.4;
291 | };
292 | name = Debug;
293 | };
294 | 2BE664932A09130800E83244 /* Release */ = {
295 | isa = XCBuildConfiguration;
296 | buildSettings = {
297 | ALWAYS_SEARCH_USER_PATHS = NO;
298 | CLANG_ANALYZER_NONNULL = YES;
299 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
300 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
301 | CLANG_ENABLE_MODULES = YES;
302 | CLANG_ENABLE_OBJC_ARC = YES;
303 | CLANG_ENABLE_OBJC_WEAK = YES;
304 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
305 | CLANG_WARN_BOOL_CONVERSION = YES;
306 | CLANG_WARN_COMMA = YES;
307 | CLANG_WARN_CONSTANT_CONVERSION = YES;
308 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
309 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
310 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
311 | CLANG_WARN_EMPTY_BODY = YES;
312 | CLANG_WARN_ENUM_CONVERSION = YES;
313 | CLANG_WARN_INFINITE_RECURSION = YES;
314 | CLANG_WARN_INT_CONVERSION = YES;
315 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
316 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
317 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
318 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
319 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
320 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
321 | CLANG_WARN_STRICT_PROTOTYPES = YES;
322 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
323 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
324 | CLANG_WARN_UNREACHABLE_CODE = YES;
325 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
326 | COPY_PHASE_STRIP = NO;
327 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
328 | ENABLE_NS_ASSERTIONS = NO;
329 | ENABLE_STRICT_OBJC_MSGSEND = YES;
330 | GCC_C_LANGUAGE_STANDARD = gnu11;
331 | GCC_NO_COMMON_BLOCKS = YES;
332 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
333 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
334 | GCC_WARN_UNDECLARED_SELECTOR = YES;
335 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
336 | GCC_WARN_UNUSED_FUNCTION = YES;
337 | GCC_WARN_UNUSED_VARIABLE = YES;
338 | MTL_ENABLE_DEBUG_INFO = NO;
339 | MTL_FAST_MATH = YES;
340 | SDKROOT = appletvos;
341 | SWIFT_COMPILATION_MODE = wholemodule;
342 | SWIFT_OPTIMIZATION_LEVEL = "-O";
343 | TVOS_DEPLOYMENT_TARGET = 16.4;
344 | VALIDATE_PRODUCT = YES;
345 | };
346 | name = Release;
347 | };
348 | 2BE664952A09130800E83244 /* Debug */ = {
349 | isa = XCBuildConfiguration;
350 | buildSettings = {
351 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
352 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
353 | CODE_SIGN_STYLE = Automatic;
354 | CURRENT_PROJECT_VERSION = 1;
355 | DEVELOPMENT_ASSET_PATHS = "\"RemoteTV/Preview Content\"";
356 | DEVELOPMENT_TEAM = 7V89NHL866;
357 | ENABLE_PREVIEWS = YES;
358 | GENERATE_INFOPLIST_FILE = YES;
359 | INFOPLIST_FILE = RemoteTV/Info.plist;
360 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
361 | INFOPLIST_KEY_UIUserInterfaceStyle = Automatic;
362 | LD_RUNPATH_SEARCH_PATHS = (
363 | "$(inherited)",
364 | "@executable_path/Frameworks",
365 | );
366 | MARKETING_VERSION = 1.0;
367 | PRODUCT_BUNDLE_IDENTIFIER = io.dodoapps.remote;
368 | PRODUCT_NAME = "$(TARGET_NAME)";
369 | SWIFT_EMIT_LOC_STRINGS = YES;
370 | SWIFT_VERSION = 5.0;
371 | TARGETED_DEVICE_FAMILY = 3;
372 | };
373 | name = Debug;
374 | };
375 | 2BE664962A09130800E83244 /* Release */ = {
376 | isa = XCBuildConfiguration;
377 | buildSettings = {
378 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
379 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
380 | CODE_SIGN_STYLE = Automatic;
381 | CURRENT_PROJECT_VERSION = 1;
382 | DEVELOPMENT_ASSET_PATHS = "\"RemoteTV/Preview Content\"";
383 | DEVELOPMENT_TEAM = 7V89NHL866;
384 | ENABLE_PREVIEWS = YES;
385 | GENERATE_INFOPLIST_FILE = YES;
386 | INFOPLIST_FILE = RemoteTV/Info.plist;
387 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
388 | INFOPLIST_KEY_UIUserInterfaceStyle = Automatic;
389 | LD_RUNPATH_SEARCH_PATHS = (
390 | "$(inherited)",
391 | "@executable_path/Frameworks",
392 | );
393 | MARKETING_VERSION = 1.0;
394 | PRODUCT_BUNDLE_IDENTIFIER = io.dodoapps.remote;
395 | PRODUCT_NAME = "$(TARGET_NAME)";
396 | SWIFT_EMIT_LOC_STRINGS = YES;
397 | SWIFT_VERSION = 5.0;
398 | TARGETED_DEVICE_FAMILY = 3;
399 | };
400 | name = Release;
401 | };
402 | 2BE664AA2A0917F500E83244 /* Debug */ = {
403 | isa = XCBuildConfiguration;
404 | buildSettings = {
405 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
406 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
407 | CODE_SIGN_STYLE = Automatic;
408 | CURRENT_PROJECT_VERSION = 1;
409 | DEVELOPMENT_ASSET_PATHS = "\"RemotePhone/Preview Content\"";
410 | DEVELOPMENT_TEAM = 7V89NHL866;
411 | ENABLE_PREVIEWS = YES;
412 | GENERATE_INFOPLIST_FILE = YES;
413 | INFOPLIST_FILE = RemotePhone/Info.plist;
414 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
415 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
416 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
417 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
418 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
419 | IPHONEOS_DEPLOYMENT_TARGET = 16.4;
420 | LD_RUNPATH_SEARCH_PATHS = (
421 | "$(inherited)",
422 | "@executable_path/Frameworks",
423 | );
424 | MARKETING_VERSION = 1.0;
425 | PRODUCT_BUNDLE_IDENTIFIER = io.dodoapps.remote;
426 | PRODUCT_NAME = "$(TARGET_NAME)";
427 | SDKROOT = iphoneos;
428 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
429 | SUPPORTS_MACCATALYST = NO;
430 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
431 | SWIFT_EMIT_LOC_STRINGS = YES;
432 | SWIFT_VERSION = 5.0;
433 | TARGETED_DEVICE_FAMILY = 1;
434 | };
435 | name = Debug;
436 | };
437 | 2BE664AB2A0917F500E83244 /* Release */ = {
438 | isa = XCBuildConfiguration;
439 | buildSettings = {
440 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
441 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
442 | CODE_SIGN_STYLE = Automatic;
443 | CURRENT_PROJECT_VERSION = 1;
444 | DEVELOPMENT_ASSET_PATHS = "\"RemotePhone/Preview Content\"";
445 | DEVELOPMENT_TEAM = 7V89NHL866;
446 | ENABLE_PREVIEWS = YES;
447 | GENERATE_INFOPLIST_FILE = YES;
448 | INFOPLIST_FILE = RemotePhone/Info.plist;
449 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
450 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
451 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
452 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
453 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
454 | IPHONEOS_DEPLOYMENT_TARGET = 16.4;
455 | LD_RUNPATH_SEARCH_PATHS = (
456 | "$(inherited)",
457 | "@executable_path/Frameworks",
458 | );
459 | MARKETING_VERSION = 1.0;
460 | PRODUCT_BUNDLE_IDENTIFIER = io.dodoapps.remote;
461 | PRODUCT_NAME = "$(TARGET_NAME)";
462 | SDKROOT = iphoneos;
463 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
464 | SUPPORTS_MACCATALYST = NO;
465 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
466 | SWIFT_EMIT_LOC_STRINGS = YES;
467 | SWIFT_VERSION = 5.0;
468 | TARGETED_DEVICE_FAMILY = 1;
469 | };
470 | name = Release;
471 | };
472 | /* End XCBuildConfiguration section */
473 |
474 | /* Begin XCConfigurationList section */
475 | 2BE664812A09130700E83244 /* Build configuration list for PBXProject "Remote" */ = {
476 | isa = XCConfigurationList;
477 | buildConfigurations = (
478 | 2BE664922A09130800E83244 /* Debug */,
479 | 2BE664932A09130800E83244 /* Release */,
480 | );
481 | defaultConfigurationIsVisible = 0;
482 | defaultConfigurationName = Release;
483 | };
484 | 2BE664942A09130800E83244 /* Build configuration list for PBXNativeTarget "RemoteTV" */ = {
485 | isa = XCConfigurationList;
486 | buildConfigurations = (
487 | 2BE664952A09130800E83244 /* Debug */,
488 | 2BE664962A09130800E83244 /* Release */,
489 | );
490 | defaultConfigurationIsVisible = 0;
491 | defaultConfigurationName = Release;
492 | };
493 | 2BE664A92A0917F500E83244 /* Build configuration list for PBXNativeTarget "RemotePhone" */ = {
494 | isa = XCConfigurationList;
495 | buildConfigurations = (
496 | 2BE664AA2A0917F500E83244 /* Debug */,
497 | 2BE664AB2A0917F500E83244 /* Release */,
498 | );
499 | defaultConfigurationIsVisible = 0;
500 | defaultConfigurationName = Release;
501 | };
502 | /* End XCConfigurationList section */
503 | };
504 | rootObject = 2BE6647E2A09130700E83244 /* Project object */;
505 | }
506 |
--------------------------------------------------------------------------------