I am trying to build a basic application to send messages between nearby iOS devices with Multipeer Connectivity Framework. I have tried many tutorials but it seems in Xcode 11 browsing nearby devices and accepting requests does not work as it did before.
Here is my view controller & delegate methods:
import UIKit
import MultipeerConnectivity
class ViewController: UIViewController,MCSessionDelegate,MCBrowserViewControllerDelegate {
var peerID: MCPeerID?
var session: MCSession?
override func viewDidLoad() {
super.viewDidLoad()
peerID = MCPeerID(displayName: UIDevice.current.name)
session = MCSession(peer: peerID!, securityIdentity: nil, encryptionPreference: .none)
session!.delegate = self
}
func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
switch state {
case .connected: print("Connected to \(peerID.displayName)")
case .connecting: print("Connecting: \(peerID.displayName)")
case .notConnected: print("Not Connected: \(peerID.displayName)")
default: print("")
}
}
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
}
func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {
}
func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) {
}
func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) {
}
func browserViewControllerDidFinish(_ browserViewController: MCBrowserViewController) {
browserViewController.dismiss(animated: true, completion: nil)
}
func browserViewControllerWasCancelled(_ browserViewController: MCBrowserViewController) {
browserViewController.dismiss(animated: true, completion: nil)
}
}
I've added 2 buttons in the UI, one for hosting and the other one for joining. Here are the methods:
@IBAction func hostBtnTapped(_ sender: Any) {
hostSession()
}
@IBAction func joinBtnTapped(_ sender: Any) {
joinSession()
}
They call:
func hostSession() {
let advertiser = MCAdvertiserAssistant(serviceType: "mg-testing", discoveryInfo: nil, session: session!)
advertiser.start()
}
func joinSession() {
let browser = MCBrowserViewController(serviceType: "mg-testing", session: session!)
browser.delegate = self
self.present(browser, animated: true, completion: nil)
}
When I compile & run it, everything looks fine. I click "Host" in my first device and enter "Join" mode in my second device (the MCBrowserViewController
shows up), but the joining device never detects the hosting device. There is no output in console and there are no errors. The joining device's "Searching..." indicator turns forever without any nearby devices showing up. What might be causing this? How can I solve it? I'm using Xcode 11.0 and iOS 12 & 13.
It seems MCAdvertiserAssistant
and MCBrowserViewController
are not updated for latest versions of iOS & Swift, hence they're not working properly.
I solved it by using MCNearbyServiceAdvertiser
instead of MCAdvertiserAssistant
and MCNearbyServiceBrowser
instead of MCBrowserViewController
. Note that by using these classes, you'll need to perform basic operations yourself such as listing founded devices, showing and handling invitation alert.. etc.
You can use these classes as below.
Definition:
var advertiser: MCNearbyServiceAdvertiser!
var browser: MCNearbyServiceBrowser!
Initialization:
advertiser = MCNearbyServiceAdvertiser(peer: peerID, discoveryInfo: nil, serviceType: "my-test")
browser = MCNearbyServiceBrowser(peer: peerID, serviceType: "my-test")
Assigning delegates & starting:
advertiser.delegate = self
advertiser.startAdvertisingPeer()
browser.delegate = self
browser.startBrowsingForPeers()
Based on the answer of my predecessor, the biggest problem seems to be the MCAdvertiserAssistant
. MCBrowserViewController
works fine fore me.
If you have code that should run based on MCAdvertiserAssistant
and MCBrowserViewController
, try to replace the MCAdvertiserAssistant
with MCNearbyServiceAdvertiser
.
So this is everything you need, if you for example work on Project 25 in the 100 days of Swift by Paul Hudson. (Which I just did when I had to find a solution for the problem)
var advertiser: MCNearbyServiceAdvertiser!
advertiser = MCNearbyServiceAdvertiser(peer: peerID, discoveryInfo: nil, serviceType: "my-test")
advertiser.delegate = self
advertiser.startAdvertisingPeer()
Add the MCNearbyServiceAdvertiserDelegate
to your protocol list and implement the delegate method:
func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) {
invitationHandler(true, mcSession)
}
This would just accept every connection but it is enough too so that it should work.
For further investigation: It seems to be a problem with the new Xcode template and the new UIApplicationSceneManifest
in the info.plist and the new SceneDelegate for the multi-windows support.
When I changed it to the old info plist and AppDelegate system MCAdvertiserAssistant
works just fine even with a new Xcode and Swift.
ps. Based on latest Xcode 11.2 Beta 2
This issue is caused by the UISceneDelegate. It should be good to go after you opt out of the UISceneDelegate and rebuild your project.
For more information about how to opt out of the UISceneDelegate, please take a look at the post: Xcode 11 - Opt out of UISceneDelegate/SwiftUI on iOS 13
For Xcode 12 and iOS 14:
Come from hackingwithSwift project 25 too, it takes me 2 hours to compare the code:( Eventually, I think it is some issue with Xcode version or iOS version.
To fix it, first replace MCAdvertiserAssistant
with MCNearbyServiceAdvertiser
.
Change startHosting method as below:
func startHosting(action: UIAlertAction) {
advertiser = MCNearbyServiceAdvertiser(peer: peerID, discoveryInfo: nil, serviceType: "hws-project25")
advertiser.delegate = self
advertiser.startAdvertisingPeer()
}
And implement this new method: I add an alertViewController to show the grant prompt because it will not appear when we do above changes.
func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) {
let ac = UIAlertController(title: "Project25", message: "'\(peerID.displayName)' wants to connect", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "Accept", style: .default, handler: { [weak self] _ in
invitationHandler(true, self?.mcSession)
}))
ac.addAction(UIAlertAction(title: "Decline", style: .cancel, handler: { _ in
invitationHandler(false, nil)
}))
present(ac, animated: true)
}
For iOS 14, You also need to add this 2 properties in info.plist if you want to use multipeer connection.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With