美文网首页
MultipeerConnectivity框架详细解析(三) —

MultipeerConnectivity框架详细解析(三) —

作者: 刀客传奇 | 来源:发表于2020-12-01 21:49 被阅读0次

    版本记录

    版本号 时间
    V1.0 2020.12.01 星期二

    前言

    MultipeerConnectivity框架支持点对点连接和附近设备的发现。是iOS 7 推出的众多新框架的一种,它拓宽了操作系统中应用的范围。其目的是使开发者可以创建通过Wi-Fi或蓝牙在近距离建立连接的应用。是在近距离设备间建立互动,交换数据和其他资源的很好的简单工具。接下来几篇我们就一起看一下这个框架。感兴趣的可以看下面几篇文章。
    1. MultipeerConnectivity框架详细解析(一) —— 基本概览(一)
    2. MultipeerConnectivity框架详细解析(二) —— 一个简单示例(一)

    源码

    1. Swift

    首先看下工程组织结构

    下面就是源码啦

    1. SceneDelegate.swift
    
    import UIKit
    import SwiftUI
    
    class SceneDelegate: UIResponder, UIWindowSceneDelegate {
      var window: UIWindow?
    
      func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let windowScene = scene as? UIWindowScene {
          let window = UIWindow(windowScene: windowScene)
          window.rootViewController = UIHostingController(rootView: ContentView())
          self.window = window
          window.makeKeyAndVisible()
        }
      }
    }
    
    2. ContentView.swift
    
    import SwiftUI
    
    struct ContentView: View {
      var body: some View {
        TabView {
          NavigationView {
            JobListView()
          }
          .navigationViewStyle(StackNavigationViewStyle())
          .tabItem {
            Image(systemName: "briefcase")
            Text("Jobs")
          }
          NavigationView {
            JoinSessionView()
          }
          .navigationViewStyle(StackNavigationViewStyle())
          .tabItem {
            Image(systemName: "bubble.left")
            Text("Messages")
          }
        }
      }
    }
    
    #if DEBUG
    struct ContentView_Previews: PreviewProvider {
      static var previews: some View {
        ContentView()
      }
    }
    #endif
    
    3. JobConnectionManager.swift
    
    import Foundation
    import MultipeerConnectivity
    
    class JobConnectionManager: NSObject, ObservableObject {
      typealias JobReceivedHandler = (JobModel) -> Void
      private static let service = "jobmanager-jobs"
    
      @Published var employees: [MCPeerID] = []
    
      private var session: MCSession
      private let myPeerId = MCPeerID(displayName: UIDevice.current.name)
      private var nearbyServiceBrowser: MCNearbyServiceBrowser
      private var nearbyServiceAdvertiser: MCNearbyServiceAdvertiser
      private let jobReceivedHandler: JobReceivedHandler?
    
      private var jobToSend: JobModel?
      private var peerInvitee: MCPeerID?
    
      init(_ jobReceivedHandler: JobReceivedHandler? = nil) {
        session = MCSession(peer: myPeerId, securityIdentity: nil, encryptionPreference: .none)
        nearbyServiceAdvertiser = MCNearbyServiceAdvertiser(
          peer: myPeerId,
          discoveryInfo: nil,
          serviceType: JobConnectionManager.service)
        nearbyServiceBrowser = MCNearbyServiceBrowser(peer: myPeerId, serviceType: JobConnectionManager.service)
        self.jobReceivedHandler = jobReceivedHandler
        super.init()
        session.delegate = self
        nearbyServiceAdvertiser.delegate = self
        nearbyServiceBrowser.delegate = self
      }
    
      func startBrowsing() {
        nearbyServiceBrowser.startBrowsingForPeers()
      }
    
      func stopBrowsing() {
        nearbyServiceBrowser.stopBrowsingForPeers()
      }
    
      var isReceivingJobs: Bool = false {
        didSet {
          if isReceivingJobs {
            nearbyServiceAdvertiser.startAdvertisingPeer()
          } else {
            nearbyServiceAdvertiser.stopAdvertisingPeer()
          }
        }
      }
    
      func invitePeer(_ peerID: MCPeerID, to job: JobModel) {
        jobToSend = job
        let context = job.name.data(using: .utf8)
        nearbyServiceBrowser.invitePeer(peerID, to: session, withContext: context, timeout: TimeInterval(120))
      }
    
      private func send(_ job: JobModel, to peer: MCPeerID) {
        do {
          let data = try JSONEncoder().encode(job)
          try session.send(data, toPeers: [peer], with: .reliable)
        } catch {
          print(error.localizedDescription)
        }
      }
    }
    
    extension JobConnectionManager: MCNearbyServiceAdvertiserDelegate {
      func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) {
        guard
          let window = UIApplication.shared.windows.first,
          let context = context,
          let jobName = String(data: context, encoding: .utf8)
        else { return }
    
        let title = "Accept \(peerID.displayName)'s Job"
        let message = "Would you like to accept: \(jobName)"
        let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
        alertController.addAction(UIAlertAction(title: "No", style: .cancel, handler: nil))
        alertController.addAction(UIAlertAction(title: "Yes", style: .default) { _ in
          invitationHandler(true, self.session)
        })
        window.rootViewController?.present(alertController, animated: true)
      }
    }
    
    extension JobConnectionManager: MCNearbyServiceBrowserDelegate {
      func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String: String]?) {
        if !employees.contains(peerID) {
          employees.append(peerID)
        }
      }
    
      func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {
        guard let index = employees.firstIndex(of: peerID) else { return }
        employees.remove(at: index)
      }
    }
    
    extension JobConnectionManager: MCSessionDelegate {
      func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
        switch state {
        case .connected:
          guard let jobToSend = jobToSend else { return }
          send(jobToSend, to: peerID)
        case .notConnected:
          print("Not connected: \(peerID.displayName)")
        case .connecting:
          print("Connecting to: \(peerID.displayName)")
        @unknown default:
          print("Unknown state: \(state)")
        }
      }
    
      func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
        guard let job = try? JSONDecoder().decode(JobModel.self, from: data) else { return }
        DispatchQueue.main.async {
          self.jobReceivedHandler?(job)
        }
      }
    
      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?) {}
    }
    
    4. ChatConnectionManager.swift
    
    import Foundation
    import MultipeerConnectivity
    
    class ChatConnectionManager: NSObject, ObservableObject {
      private static let service = "jobmanager-chat"
    
      @Published var messages: [ChatMessage] = []
      @Published var peers: [MCPeerID] = []
      @Published var connectedToChat = false
    
      let myPeerId = MCPeerID(displayName: UIDevice.current.name)
      private var advertiserAssistant: MCNearbyServiceAdvertiser?
      private var session: MCSession?
      private var isHosting = false
    
      func send(_ message: String) {
        let chatMessage = ChatMessage(displayName: myPeerId.displayName, body: message)
        messages.append(chatMessage)
        guard
          let session = session,
          let data = message.data(using: .utf8),
          !session.connectedPeers.isEmpty
        else { return }
    
        do {
          try session.send(data, toPeers: session.connectedPeers, with: .reliable)
        } catch {
          print(error.localizedDescription)
        }
      }
    
      func sendHistory(to peer: MCPeerID) {
        let tempFile = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("messages.data")
        guard let historyData = try? JSONEncoder().encode(messages) else { return }
        try? historyData.write(to: tempFile)
        session?.sendResource(at: tempFile, withName: "Chat_History", toPeer: peer) { error in
          if let error = error {
            print(error.localizedDescription)
          }
        }
      }
    
      func join() {
        peers.removeAll()
        messages.removeAll()
        session = MCSession(peer: myPeerId, securityIdentity: nil, encryptionPreference: .required)
        session?.delegate = self
        guard
          let window = UIApplication.shared.windows.first,
          let session = session
        else { return }
    
        let mcBrowserViewController = MCBrowserViewController(serviceType: ChatConnectionManager.service, session: session)
        mcBrowserViewController.delegate = self
        window.rootViewController?.present(mcBrowserViewController, animated: true)
      }
    
      func host() {
        isHosting = true
        peers.removeAll()
        messages.removeAll()
        connectedToChat = true
        session = MCSession(peer: myPeerId, securityIdentity: nil, encryptionPreference: .required)
        session?.delegate = self
        advertiserAssistant = MCNearbyServiceAdvertiser(
          peer: myPeerId,
          discoveryInfo: nil,
          serviceType: ChatConnectionManager.service)
        advertiserAssistant?.delegate = self
        advertiserAssistant?.startAdvertisingPeer()
      }
    
      func leaveChat() {
        isHosting = false
        connectedToChat = false
        advertiserAssistant?.stopAdvertisingPeer()
        messages.removeAll()
        session = nil
        advertiserAssistant = nil
      }
    }
    
    extension ChatConnectionManager: MCNearbyServiceAdvertiserDelegate {
      func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) {
        invitationHandler(true, session)
      }
    }
    
    extension ChatConnectionManager: MCSessionDelegate {
      func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
        guard let message = String(data: data, encoding: .utf8) else { return }
        let chatMessage = ChatMessage(displayName: peerID.displayName, body: message)
        DispatchQueue.main.async {
          self.messages.append(chatMessage)
        }
      }
    
      func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
        switch state {
        case .connected:
          if !peers.contains(peerID) {
            DispatchQueue.main.async {
              self.peers.insert(peerID, at: 0)
            }
            if isHosting {
              sendHistory(to: peerID)
            }
          }
        case .notConnected:
          DispatchQueue.main.async {
            if let index = self.peers.firstIndex(of: peerID) {
              self.peers.remove(at: index)
            }
            if self.peers.isEmpty && !self.isHosting {
              self.connectedToChat = false
            }
          }
        case .connecting:
          print("Connecting to: \(peerID.displayName)")
        @unknown default:
          print("Unknown state: \(state)")
        }
      }
    
      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) {
        print("Receiving chat history")
      }
    
      func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) {
        guard
          let localURL = localURL,
          let data = try? Data(contentsOf: localURL),
          let messages = try? JSONDecoder().decode([ChatMessage].self, from: data)
        else { return }
    
        DispatchQueue.main.async {
          self.messages.insert(contentsOf: messages, at: 0)
        }
      }
    }
    
    extension ChatConnectionManager: MCBrowserViewControllerDelegate {
      func browserViewControllerDidFinish(_ browserViewController: MCBrowserViewController) {
        browserViewController.dismiss(animated: true) {
          self.connectedToChat = true
        }
      }
    
      func browserViewControllerWasCancelled(_ browserViewController: MCBrowserViewController) {
        session?.disconnect()
        browserViewController.dismiss(animated: true)
      }
    }
    
    5. JobModel.swift
    
    import Foundation
    
    class JobListStore: ObservableObject {
      @Published var jobs: [JobModel] = []
    }
    
    struct JobModel: Codable, Identifiable {
      var id = UUID()
      let name: String
      let dueDate: Date
      let payout: String
    
      init(name: String, dueDate: Date, payout: String) {
        let payoutNumber = NSNumber(value: Int(payout) ?? 0)
        let payoutString = NumberFormatter.currency.string(from: payoutNumber) ?? ""
        self.name = name
        self.dueDate = dueDate
        self.payout = payoutString
      }
    
      func data() -> Data? {
        let encoder = JSONEncoder()
        return try? encoder.encode(self)
      }
    }
    
    6. ChatMessage.swift
    
    import UIKit
    
    struct ChatMessage: Identifiable, Equatable, Codable {
      var id = UUID()
      let displayName: String
      let body: String
      var time = Date()
    
      var isUser: Bool {
        return displayName == UIDevice.current.name
      }
    }
    
    7. JobListView.swift
    
    import SwiftUI
    
    struct JobListView: View {
      @ObservedObject var jobListStore: JobListStore
      @ObservedObject var jobConnectionManager: JobConnectionManager
      @State private var showAddJob = false
    
      init(jobListStore: JobListStore = JobListStore()) {
        self.jobListStore = jobListStore
        jobConnectionManager = JobConnectionManager { job in
          jobListStore.jobs.append(job)
        }
      }
    
      var body: some View {
        List {
          Section(
            header: headerView,
            footer: footerView) {
            ForEach(jobListStore.jobs) { job in
              JobListRowView(job: job)
                .environmentObject(jobConnectionManager)
            }
            .onDelete { indexSet in
              jobListStore.jobs.remove(atOffsets: indexSet)
            }
          }
        }
        .listStyle(InsetGroupedListStyle())
        .navigationTitle("Jobs")
        .sheet(isPresented: $showAddJob) {
          NavigationView {
            AddJobView()
              .environmentObject(jobListStore)
          }
        }
      }
    
      var headerView: some View {
        Toggle("Receive Jobs", isOn: $jobConnectionManager.isReceivingJobs)
      }
    
      var footerView: some View {
        Button(
          action: {
            showAddJob = true
          }, label: {
            Label("Add Job", systemImage: "plus.circle")
          })
          .buttonStyle(FooterButtonStyle())
      }
    }
    
    #if DEBUG
    struct JobListViewPreview: PreviewProvider {
      static var previews: some View {
        NavigationView {
          JobListView(jobListStore: JobListStore())
        }
      }
    }
    #endif
    
    8. JobListRowView.swift
    
    import SwiftUI
    
    struct JobListRowView: View {
      @EnvironmentObject var jobConnectionManager: JobConnectionManager
      let job: JobModel
    
      var body: some View {
        NavigationLink(
          destination:
            JobView(job: job)
            .environmentObject(jobConnectionManager)) {
          HStack {
            VStack(alignment: .leading) {
              Text(job.name)
                .font(.title3)
              Text("\(job.dueDate, formatter: DateFormatter.dueDateFormatter)")
                .font(.caption)
            }
            Spacer()
            Text(job.payout)
              .font(.headline)
          }
        }
      }
    }
    
    #if DEBUG
    struct JobListRowView_Previews: PreviewProvider {
      static var previews: some View {
        JobListRowView(job: JobModel(name: "Mock Job", dueDate: Date(), payout: "$25.00"))
          .environmentObject(JobConnectionManager())
      }
    }
    #endif
    
    9. JobView.swift
    
    import SwiftUI
    
    struct JobView: View {
      let job: JobModel
      @EnvironmentObject var jobConnectionManager: JobConnectionManager
    
      var body: some View {
        List {
          HStack {
            Label(
              title: {
                Text("Due Date")
                  .font(.headline)
              },
              icon: {
                Image(systemName: "calendar")
              })
            Spacer()
            Text("\(job.dueDate, formatter: DateFormatter.dueDateFormatter)")
          }
          HStack {
            Label(
              title: {
                Text("Payout")
                  .font(.headline)
              },
              icon: {
                Image(systemName: "creditcard")
              })
            Spacer()
            Text(job.payout)
          }
          Section(
            header: HStack(spacing: 8) {
              Text("Available Employees")
              Spacer()
              ProgressView()
            }) {
            ForEach(jobConnectionManager.employees, id: \.self) { employee in
              HStack {
                Text(employee.displayName)
                  .font(.headline)
                Spacer()
                Image(systemName: "arrowshape.turn.up.right.fill")
              }
              .onTapGesture {
                jobConnectionManager.invitePeer(employee, to: job)
              }
            }
          }
        }
        .listStyle(InsetGroupedListStyle())
        .navigationTitle(job.name)
        .onAppear {
          jobConnectionManager.startBrowsing()
        }
        .onDisappear {
          jobConnectionManager.stopBrowsing()
        }
      }
    }
    
    #if DEBUG
    struct JobView_Previews: PreviewProvider {
      static var previews: some View {
        NavigationView {
          JobView(job: JobModel(name: "Test Job", dueDate: Date(), payout: "$25.00"))
            .environmentObject(JobConnectionManager())
        }
      }
    }
    #endif
    
    10. AddJobView.swift
    
    import SwiftUI
    
    struct AddJobView: View {
      @EnvironmentObject var jobListStore: JobListStore
      @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
      @State private var jobName = ""
      @State private var dueDate = Date()
      @State private var payout = ""
    
      var body: some View {
        Form {
          TextField("Job Name", text: $jobName)
          DatePicker("Due Date", selection: $dueDate, in: Date()..., displayedComponents: .date)
          HStack {
            Text(NumberFormatter.currency.currencySymbol)
            TextField("Payout", text: $payout)
              .keyboardType(.numberPad)
          }
          Button("Save") {
            let job = JobModel(name: jobName, dueDate: dueDate, payout: payout)
            jobListStore.jobs.append(job)
            presentationMode.wrappedValue.dismiss()
          }
          .disabled(jobName.isEmpty || payout.isEmpty)
        }
        .listStyle(InsetGroupedListStyle())
        .navigationBarTitle("Add Job", displayMode: .inline)
        .toolbar {
          ToolbarItem(placement: .cancellationAction) {
            Button("Cancel") {
              presentationMode.wrappedValue.dismiss()
            }
          }
        }
      }
    }
    
    #if DEBUG
    struct AddJobView_Previews: PreviewProvider {
      static var previews: some View {
        NavigationView {
          AddJobView()
            .environmentObject(JobListStore())
        }
      }
    }
    #endif
    
    11. JoinSessionView.swift
    
    import SwiftUI
    
    struct JoinSessionView: View {
      @ObservedObject private var chatConnectionManager = ChatConnectionManager()
    
      var body: some View {
        VStack(spacing: 24) {
          Image(systemName: "network")
            .resizable()
            .frame(width: 100, height: 100)
          Button(
            action: {
              chatConnectionManager.join()
            }, label: {
              Label("Join a Chat Session", systemImage: "arrow.up.right.and.arrow.down.left.rectangle")
            })
          .buttonStyle(MultipeerButtonStyle())
          Button(
            action: {
              chatConnectionManager.host()
            }, label: {
              Label("Host a Chat Session", systemImage: "plus.circle")
            })
          .buttonStyle(MultipeerButtonStyle())
          NavigationLink(
            destination: ChatView()
              .environmentObject(chatConnectionManager),
            isActive: $chatConnectionManager.connectedToChat) {
              EmptyView()
          }
        }
        .navigationTitle("Chat")
      }
    }
    
    #if DEBUG
    struct JoinSessionView_Previews: PreviewProvider {
      static var previews: some View {
        NavigationView {
          JoinSessionView()
        }
      }
    }
    #endif
    
    12. ChatView.swift
    
    import SwiftUI
    
    struct ChatView: View {
      @EnvironmentObject var chatConnectionManager: ChatConnectionManager
      @State private var messageText = ""
    
      var body: some View {
        VStack {
          chatInfoView
          ChatListView()
            .environmentObject(chatConnectionManager)
          messageField
        }
        .navigationBarTitle("Chat", displayMode: .inline)
        .toolbar {
          ToolbarItem(placement: .navigationBarLeading) {
            Button("Leave") {
              chatConnectionManager.leaveChat()
            }
          }
        }
        .navigationBarBackButtonHidden(true)
      }
    
      private var messageField: some View {
        VStack(spacing: 0) {
          Divider()
          // swiftlint:disable:next trailing_closure
          TextField("Enter Message", text: $messageText, onCommit: {
            guard !messageText.isEmpty else { return }
            chatConnectionManager.send(messageText)
            messageText = ""
          })
          .padding()
        }
      }
    
      private var chatInfoView: some View {
        VStack(alignment: .leading) {
          Divider()
          HStack {
            Text("People in chat:")
              .fixedSize(horizontal: true, vertical: false)
              .font(.headline)
            if chatConnectionManager.peers.isEmpty {
              Text("Empty")
                .font(Font.caption.italic())
                .foregroundColor(Color("rw-dark"))
            } else {
              chatParticipants
            }
          }
          .padding(.top, 8)
          .padding(.leading, 16)
          Divider()
        }
        .frame(height: 44)
      }
    
      private var chatParticipants: some View {
        ScrollView(.horizontal, showsIndicators: false) {
          HStack {
            ForEach(chatConnectionManager.peers, id: \.self) { peer in
              Text(peer.displayName)
                .padding(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/, 6)
                .background(Color("rw-dark"))
                .foregroundColor(.white)
                .font(Font.body.bold())
                .cornerRadius(9)
            }
          }
        }
      }
    }
    
    #if DEBUG
    import MultipeerConnectivity
    struct ChatView_Previews: PreviewProvider {
      static let chatConnectionManager = ChatConnectionManager()
    
      static var previews: some View {
        NavigationView {
          ChatView()
            .environmentObject(chatConnectionManager)
            .onAppear {
              chatConnectionManager.peers.append(MCPeerID(displayName: "Test Peer"))
            }
        }
      }
    }
    #endif
    
    13. ChatListView.swift
    
    import SwiftUI
    
    struct ChatListView: View {
      @EnvironmentObject var chatConnectionManager: ChatConnectionManager
    
      var body: some View {
        ScrollView {
          ScrollViewReader { reader in
            VStack(alignment: .leading, spacing: 20) {
              ForEach(chatConnectionManager.messages) { message in
                MessageBodyView(message: message)
                  .onAppear {
                    if message == chatConnectionManager.messages.last {
                      reader.scrollTo(message.id)
                    }
                  }
              }
            }
            .padding(16)
          }
        }
        .background(Color(UIColor.systemBackground))
      }
    }
    
    #if DEBUG
    struct ChatListView_Previews: PreviewProvider {
      static var previews: some View {
        ChatListView()
          .environmentObject(ChatConnectionManager())
      }
    }
    #endif
    
    14. MessageBodyView.swift
    
    import SwiftUI
    
    struct MessageBodyView: View {
      let message: ChatMessage
    
      var body: some View {
        HStack {
          if message.isUser {
            Spacer()
          }
          VStack(alignment: message.isUser ? .trailing : .leading, spacing: 4) {
            Text(message.body)
              .font(.body)
              .padding(8)
              .foregroundColor(.white)
              .background(message.isUser ? .green : Color("rw-dark"))
              .cornerRadius(9)
            TimestampView(message: message)
          }
        }
      }
    }
    
    #if DEBUG
    struct MessageBodyView_Previews: PreviewProvider {
      static var previews: some View {
        VStack {
          MessageBodyView(message: ChatMessage(displayName: "User 1", body: "Test"))
          MessageBodyView(message: ChatMessage(displayName: UIDevice.current.name, body: "Test"))
        }
        .padding()
      }
    }
    #endif
    
    15. TimestampView.swift
    
    import SwiftUI
    
    struct TimestampView: View {
      let message: ChatMessage
    
      var body: some View {
        HStack(spacing: 2) {
          Text(message.displayName)
          Text("@")
          Text("\(message.time, formatter: DateFormatter.timestampFormatter)")
          if !message.isUser {
            Spacer()
          }
        }
        .font(.caption)
        .foregroundColor(Color("rw-dark"))
      }
    }
    
    #if DEBUG
    struct TimestampView_Previews: PreviewProvider {
      static var previews: some View {
        VStack {
          TimestampView(message: ChatMessage(displayName: "User 1", body: "Test"))
          TimestampView(message: ChatMessage(displayName: UIDevice.current.name, body: "Test"))
        }
        .padding()
      }
    }
    #endif
    
    16. ButtonStyles.swift
    
    import SwiftUI
    
    struct MultipeerButtonStyle: ButtonStyle {
      func makeBody(configuration: Configuration) -> some View {
        configuration.label
          .padding()
          .font(.headline)
          .background(configuration.isPressed ? Color("rw-dark") : Color.accentColor)
          .cornerRadius(9.0)
          .foregroundColor(.white)
      }
    }
    
    struct ChatMessageButtonStyle: ButtonStyle {
      func makeBody(configuration: Configuration) -> some View {
        HStack {
          Spacer()
          configuration.label
          Spacer()
        }
        .padding(8)
        .background(configuration.isPressed ? Color("rw-dark") : Color.green)
        .cornerRadius(9.0)
        .foregroundColor(.white)
      }
    }
    
    struct FooterButtonStyle: ButtonStyle {
      func makeBody(configuration: Configuration) -> some View {
        configuration.label
          .foregroundColor(configuration.isPressed ? Color("rw-dark") : .accentColor)
          .font(.headline)
          .padding(8)
      }
    }
    
    17. Formatters.swift
    
    import Foundation
    
    extension NumberFormatter {
      static var currency: NumberFormatter = {
        let numberFormatter = NumberFormatter()
        numberFormatter.numberStyle = .currency
        return numberFormatter
      }()
    }
    
    extension DateFormatter {
      static var dueDateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        formatter.timeStyle = .none
        return formatter
      }()
    
      static var timestampFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .none
        formatter.timeStyle = .short
        return formatter
      }()
    }
    

    后记

    本篇主要讲述了MultipeerConnectivity的一个简单示例,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

          本文标题:MultipeerConnectivity框架详细解析(三) —

          本文链接:https://www.haomeiwen.com/subject/wudewktx.html