├── screenshot.png
├── MarchThirteen
├── MarchThirteen.xcodeproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ └── MarchThirteen.xcscheme
│ └── project.pbxproj
└── MarchThirteen
│ ├── WalkieTalkie
│ ├── CommunicatorOutput.swift
│ ├── Peer.swift
│ ├── CommunicatorPayload.swift
│ ├── CommunicatorMessage.swift
│ ├── Communicator.swift
│ └── Service.swift
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Info.plist
│ ├── Base.lproj
│ └── LaunchScreen.storyboard
│ ├── Utils
│ └── UIImageExtension.swift
│ ├── ChatRoom.swift
│ └── ViewController.swift
├── MarchThirteen.xcworkspace
└── contents.xcworkspacedata
├── LICENSE
├── Podfile.lock
├── .gitignore
├── Podfile
└── README.md
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zweigraf/march-thirteen/HEAD/screenshot.png
--------------------------------------------------------------------------------
/MarchThirteen/MarchThirteen.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MarchThirteen.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/MarchThirteen/MarchThirteen/WalkieTalkie/CommunicatorOutput.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommunicatorOutput.swift
3 | // MarchThirteen
4 | //
5 | // Created by Luis Reisewitz on 18.03.17.
6 | // Copyright © 2017 ZweiGraf. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol CommunicatorOutput {
12 | associatedtype PayloadType: CommunicatorPayload
13 |
14 | init(from: CommunicatorMessage)
15 | }
16 |
--------------------------------------------------------------------------------
/MarchThirteen/MarchThirteen/WalkieTalkie/Peer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Peer.swift
3 | // MarchThirteen
4 | //
5 | // Created by Luis Reisewitz on 18.03.17.
6 | // Copyright © 2017 ZweiGraf. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import MultipeerConnectivity
11 |
12 | /// Represents one peer.
13 | public struct Peer {
14 | /// Name that should be used when displaying this peer.
15 | let name: String
16 | }
17 |
18 | // MARK: - MCPeerID compatibility
19 | internal extension Peer {
20 | init(peerID: MCPeerID) {
21 | self.init(name: peerID.displayName)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/MarchThirteen/MarchThirteen/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // MarchThirteen
4 | //
5 | // Created by Luis Reisewitz on 13.03.17.
6 | // Copyright © 2017 ZweiGraf. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SuperDelegate
11 |
12 | @UIApplicationMain
13 | class AppDelegate: SuperDelegate, ApplicationLaunched {
14 | var window: UIWindow?
15 |
16 | func setupApplication() {
17 |
18 | }
19 |
20 | func loadInterface(launchItem: LaunchItem) {
21 | let window = UIWindow()
22 | let navigationController = UINavigationController(rootViewController: ViewController())
23 | window.rootViewController = navigationController
24 | setup(mainWindow: window)
25 | self.window = window
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/MarchThirteen/MarchThirteen/WalkieTalkie/CommunicatorPayload.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommunicatorPayload.swift
3 | // MarchThirteen
4 | //
5 | // Created by Luis Reisewitz on 18.03.17.
6 | // Copyright © 2017 ZweiGraf. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol CommunicatorPayload {
12 | /// Initializes a new object from the given dictionary. Should return nil
13 | /// if the dictionary cannot be decoded.
14 | ///
15 | /// - Parameters:
16 | /// - from: A dictionary representation retrieved via `dictionaryRepresentation`
17 | /// of the same type.
18 | init?(from dictionary: [String: Any])
19 |
20 | /// Encodes this object as a dictionary. Dictionary layout can vary, but
21 | /// this type should be able to decode that dictionary via `init?(from:)`
22 | /// into an instance of itself. The values & keys in the dictionary must be
23 | /// NSCoding compliant.
24 | var dictionaryRepresentation: [String: Any] { get }
25 | }
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Luis Reisewitz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - JSQMessagesViewController (7.3.4)
3 | - LambdaKit (0.0.3)
4 | - PureLayout (3.0.2)
5 | - Sensitive (3.0)
6 | - SuperDelegate (0.9.0)
7 |
8 | DEPENDENCIES:
9 | - JSQMessagesViewController (from `https://github.com/jessesquires/JSQMessagesViewController.git`, branch `develop`)
10 | - LambdaKit (~> 0.0.3)
11 | - PureLayout (~> 3.0.2)
12 | - Sensitive (~> 3.0)
13 | - SuperDelegate (~> 0.9.0)
14 |
15 | EXTERNAL SOURCES:
16 | JSQMessagesViewController:
17 | :branch: develop
18 | :git: https://github.com/jessesquires/JSQMessagesViewController.git
19 |
20 | CHECKOUT OPTIONS:
21 | JSQMessagesViewController:
22 | :commit: 45fdac769d7354ad19e5990dadbfee285a2e45ee
23 | :git: https://github.com/jessesquires/JSQMessagesViewController.git
24 |
25 | SPEC CHECKSUMS:
26 | JSQMessagesViewController: c11b9e77372ab72c45c67311f6da7342e32df1e8
27 | LambdaKit: 87d603283c5915077fc09a8cbba03b4c2a48d2bf
28 | PureLayout: 4d550abe49a94f24c2808b9b95db9131685fe4cd
29 | Sensitive: dee25cff31a5420e9fa4d4e2f0e1cef8a46f701f
30 | SuperDelegate: b70c7afddf8525217d0e074b850b8ea3cc6f8abd
31 |
32 | PODFILE CHECKSUM: feaef47745e6df85072aee06b7875154afef9ac3
33 |
34 | COCOAPODS: 1.2.0
35 |
--------------------------------------------------------------------------------
/MarchThirteen/MarchThirteen/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/MarchThirteen/MarchThirteen/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIRequiredDeviceCapabilities
26 |
27 | armv7
28 |
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xcuserstate
23 |
24 | ## Obj-C/Swift specific
25 | *.hmap
26 | *.ipa
27 | *.dSYM.zip
28 | *.dSYM
29 |
30 | ## Playgrounds
31 | timeline.xctimeline
32 | playground.xcworkspace
33 |
34 | # Swift Package Manager
35 | #
36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
37 | # Packages/
38 | .build/
39 |
40 | # CocoaPods
41 | #
42 | # We recommend against adding the Pods directory to your .gitignore. However
43 | # you should judge for yourself, the pros and cons are mentioned at:
44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
45 | #
46 | Pods/
47 |
48 | # Carthage
49 | #
50 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
51 | # Carthage/Checkouts
52 |
53 | Carthage/Build
54 |
55 | # fastlane
56 | #
57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
58 | # screenshots whenever they are needed.
59 | # For more information about the recommended setup visit:
60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
61 |
62 | fastlane/report.xml
63 | fastlane/Preview.html
64 | fastlane/screenshots
65 | fastlane/test_output
66 |
--------------------------------------------------------------------------------
/MarchThirteen/MarchThirteen/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | project 'MarchThirteen/MarchThirteen.xcodeproj'
2 |
3 | # Uncomment this line to define a global platform for your project
4 | platform :ios, '10.0'
5 |
6 | target 'MarchThirteen' do
7 | # Comment this line if you're not using Swift and don't want to use dynamic frameworks
8 | use_frameworks!
9 |
10 | # Pods for MarchThirteen
11 |
12 | # SuperDelegate provides a clean application delegate interface and protects you from bugs in the application lifecycle
13 | # https://github.com/square/SuperDelegate
14 | pod 'SuperDelegate', '~> 0.9.0'
15 |
16 | # The ultimate API for iOS & OS X Auto Layout — impressively simple, immensely powerful. Objective-C and Swift compatible.
17 | # https://github.com/PureLayout/PureLayout
18 | pod 'PureLayout', '~> 3.0.2'
19 |
20 | # Fresh look at work with gestures in Swift.
21 | # https://github.com/igormatyushkin014/Sensitive
22 | pod 'Sensitive', '~> 3.0'
23 |
24 | # An elegant messages UI library for iOS
25 | # https://github.com/jessesquires/JSQMessagesViewController
26 | pod 'JSQMessagesViewController', :git => 'https://github.com/jessesquires/JSQMessagesViewController.git', :branch => 'develop'
27 |
28 | # Modern Swift API for NSUserDefaults
29 | # https://github.com/radex/SwiftyUserDefaults
30 | # Uncomment to use
31 | # pod 'SwiftyUserDefaults', '~> 3.0.0'
32 |
33 | # A clean and lightweight progress HUD for your iOS and tvOS app.
34 | # https://github.com/SVProgressHUD/SVProgressHUD
35 | # Uncomment to use
36 | # pod 'SVProgressHUD', '~> 2.1.2'
37 |
38 | # Closures on most used UIKit methods
39 | # https://github.com/Reflejo/LambdaKit
40 | pod 'LambdaKit', '~> 0.0.3'
41 |
42 | # Intuitive date handling in Swift
43 | # https://github.com/naoty/Timepiece
44 | # Uncomment to use
45 | # pod 'Timepiece', '~> 1.1.0'
46 |
47 | # Crash reporting & Analytics
48 | # https://fabric.io/kits/ios/crashlytics/install
49 | # Uncomment to use
50 | # pod 'Fabric', '~> 1.6.11'
51 | # pod 'Crashlytics', '~> 3.8.3'
52 | end
53 |
--------------------------------------------------------------------------------
/MarchThirteen/MarchThirteen/Utils/UIImageExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImageExtension.swift
3 | // MarchThirteen
4 | //
5 | // Created by Luis Reisewitz on 21.03.17.
6 | // Copyright © 2017 ZweiGraf. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CoreGraphics
11 |
12 | public extension UIImage {
13 | static func initialsImage(for string: String, size: CGSize = CGSize(width: 30, height: 30)) -> UIImage? {
14 | let initials = string.components(separatedBy: CharacterSet.whitespaces.union(.punctuationCharacters))
15 | .map {
16 | let startIndex = $0.startIndex
17 | let secondIndex = $0.index(after: startIndex)
18 | return $0.substring(to: secondIndex)
19 | }.joined().uppercased()
20 |
21 | UIGraphicsBeginImageContextWithOptions(size, true, UIScreen.main.scale)
22 | guard let context = UIGraphicsGetCurrentContext() else {
23 | return nil
24 | }
25 |
26 | context.setFillColor(UIColor.darkGray.cgColor)
27 | context.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height))
28 |
29 | let paragraphStyle = NSMutableParagraphStyle()
30 | paragraphStyle.alignment = .center
31 | let attrs = [NSFontAttributeName: UIFont(name: "HelveticaNeue-Bold", size: 12)!,
32 | NSParagraphStyleAttributeName: paragraphStyle,
33 | NSForegroundColorAttributeName: UIColor.white]
34 |
35 | let attributedString = NSAttributedString(string: initials, attributes: attrs)
36 | let stringSize = attributedString.size()
37 | let xOffset = (size.width - stringSize.width) / 2
38 | let yOffset = (size.height - stringSize.height) / 2
39 |
40 | attributedString.draw(in: CGRect(x: xOffset, y: yOffset, width: stringSize.width, height: stringSize.height))
41 |
42 | guard let cgImage = context.makeImage() else {
43 | return nil
44 | }
45 | return UIImage(cgImage: cgImage)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/MarchThirteen/MarchThirteen/WalkieTalkie/CommunicatorMessage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommunicatorMessage.swift
3 | // MarchThirteen
4 | //
5 | // Created by Luis Reisewitz on 18.03.17.
6 | // Copyright © 2017 ZweiGraf. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | fileprivate enum DictionaryKeys: String {
12 | case meta
13 | case payload
14 | }
15 |
16 | /// Represents a message.
17 | public struct CommunicatorMessage {
18 | /// The payload of the message as sent by the other party.
19 | public let payload: PayloadType
20 | /// The peer that sent this message.
21 | public let peer: Peer
22 |
23 | // TODO: add timestamps & figure out way to synchronise devices times
24 | }
25 |
26 | // MARK: - ↔️ Data In/Out ↔️
27 | internal extension CommunicatorMessage {
28 | private init?(from dictionary: [String : Any], peer: Peer) {
29 | guard let payloadDict = dictionary[DictionaryKeys.payload.rawValue] as? [String: Any],
30 | let payload = PayloadType(from: payloadDict) else {
31 | return nil
32 | }
33 | self.init(payload: payload, peer: peer)
34 | }
35 |
36 | private var dictionaryRepresentation: [String : Any] {
37 | // Wrap payload in dictionary to later attach metadata for ourselves
38 | // Do not serialise the peer, that will not be transferred.
39 | let dictionary: [String: Any] = [
40 | DictionaryKeys.payload.rawValue: payload.dictionaryRepresentation
41 | ]
42 | return dictionary
43 | }
44 |
45 | init?(from data: Data, peer: Peer) {
46 | guard let dictionary = NSKeyedUnarchiver.unarchiveObject(with: data) as? [String: Any] else {
47 | return nil
48 | }
49 | self.init(from: dictionary, peer: peer)
50 | }
51 |
52 | var dataRepresentation: Data {
53 | let dictionary = dictionaryRepresentation
54 | let data = NSKeyedArchiver.archivedData(withRootObject: dictionary)
55 | return data
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # march-thirteen
2 | Local Chat Application with MultiPeerConnectivity.
3 |
4 | ## How it works
5 |
6 | The app opens a service via MultiPeerConnectivity and at the same time advertises
7 | itself as a peer and browses for other peers. If any peers are found, invitations
8 | are sent out automatically. Received invitations are accepted automatically.
9 |
10 | Messages can then be send to all connected peers, sort of like a group chat.
11 |
12 | No additional networking logic on top of MultiPeerConnectivity is implemented (yet).
13 |
14 | ## Screenshot
15 |
16 | 
17 |
18 | ## WalkieTalkie
19 |
20 | All of the networking logic is contained in the WalkieTalkie folder which might
21 | later be refactored into a separate library.
22 |
23 | The main component is the `Communicator` class. This is a generic class
24 | encapsulating networking. You use this to send and receive messages.
25 |
26 | Messages are serialized & deserialized using a combination of generic protocols.
27 |
28 | `Communicator` is specialized on the `CommunicatorOutput` protocol. This can be
29 | anything you like it to, e.g. in this application it represents a `ChatEvent`.
30 | The `CommunicatorOutput` protocol also defines a payload type, which is the actual
31 | object that will be sent over the wire.
32 |
33 | Serialization is done via the `CommunicatorPayload` protocol. A payload has to
34 | know how to represent itself as a dictionary and how to construct itself from
35 | such a dictionary.
36 |
37 | This dictionary is then sent over the wire and arrives on the other side. There
38 | the `Communicator` creates a `CommunicatorMessage` object, which includes the
39 | deserialized payload and some meta information (like the associated peer).
40 |
41 | This message object is used to instantiate an `CommunicatorOutput` instance
42 | which takes the message and transforms it into an application level object.
43 |
44 | This all plays together to create a seamless serialisation & deserialisation logic
45 | without any action needed on the calling side.
46 |
47 | ### Example Usage
48 |
49 | For a concrete usage example please see the `ChatRoom.swift` file. There all the
50 | application level objects for the chat are defined implementing the needed
51 | protocols.
--------------------------------------------------------------------------------
/MarchThirteen/MarchThirteen/WalkieTalkie/Communicator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Communicator.swift
3 | // MarchThirteen
4 | //
5 | // Created by Luis Reisewitz on 18.03.17.
6 | // Copyright © 2017 ZweiGraf. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import MultipeerConnectivity
12 |
13 | // MARK: - Type Def
14 | public class Communicator {
15 | // MARK: Public Properties
16 | var messageReceived: MessageReceivedCallback?
17 | var messageSent: MessageSentCallback?
18 | var peersChanged: PeersChangedCallback?
19 |
20 | init(identifier: String, ownPeerID: String) {
21 | let peerID = MCPeerID(displayName: ownPeerID)
22 | self.ownPeerID = peerID
23 |
24 | service = Service(with: identifier, as: peerID)
25 |
26 | service.dataReceived = didReceive
27 | service.peersChanged = peersChanged
28 |
29 | service.start()
30 | }
31 |
32 | // MARK: Type Aliases
33 | fileprivate typealias PayloadType = OutputType.PayloadType
34 | typealias Message = CommunicatorMessage
35 | typealias Output = OutputType
36 | typealias MessageReceivedCallback = (OutputType) -> Void
37 | typealias MessageSentCallback = (OutputType) -> Void
38 | typealias PeersChangedCallback = ([Peer]) -> Void
39 |
40 | // MARK: Private Properties
41 | fileprivate let service: Service
42 | fileprivate let ownPeerID: MCPeerID
43 | }
44 |
45 | // MARK: - ⬆️ Sending ⬆️
46 | public extension Communicator {
47 | func send(message: PayloadType) {
48 | let message = Message(payload: message, peer: Peer(peerID: ownPeerID))
49 | let data = message.dataRepresentation
50 | do {
51 | try service.send(data: data)
52 | let output = OutputType(from: message)
53 | messageSent?(output)
54 | } catch {}
55 | }
56 | }
57 |
58 | // MARK: - 📞 Callbacks 📞
59 | fileprivate extension Communicator {
60 | func didReceive(data: Data, from peer: MCPeerID) {
61 | guard let message = Message(from: data, peer: Peer(peerID: peer)) else {
62 | return
63 | }
64 | let output = OutputType(from: message)
65 | messageReceived?(output)
66 | }
67 |
68 | func peersChanged(peers: [MCPeerID]) {
69 | let peers = peers.map { Peer(peerID: $0) }
70 | peersChanged?(peers)
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/MarchThirteen/MarchThirteen.xcodeproj/xcshareddata/xcschemes/MarchThirteen.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/MarchThirteen/MarchThirteen/ChatRoom.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChatRoom.swift
3 | // MarchThirteen
4 | //
5 | // Created by Luis Reisewitz on 18.03.17.
6 | // Copyright © 2017 ZweiGraf. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | enum Payload {
12 | case typingStarted
13 | case message(text: String)
14 | }
15 |
16 | extension Payload: CommunicatorPayload {
17 | init?(from dictionary: [String : Any]) {
18 | guard let type = dictionary["type"] as? String else {
19 | return nil
20 | }
21 |
22 | switch type {
23 | case "typingStarted":
24 | self = .typingStarted
25 | case "message":
26 | guard let message = dictionary["message"] as? String else {
27 | return nil
28 | }
29 | self = .message(text: message)
30 | default:
31 | return nil
32 | }
33 | }
34 |
35 | var dictionaryRepresentation: [String : Any] {
36 | switch self {
37 | case .typingStarted:
38 | return ["type": "typingStarted"]
39 | case .message(let text):
40 | return ["type": "message", "message": text]
41 | }
42 | }
43 | }
44 |
45 | // Could also be an enum
46 | class ChatEvent: NSObject, CommunicatorOutput {
47 | typealias PayloadType = Payload
48 |
49 | let event: PayloadType
50 | let username: String
51 |
52 | init(event: PayloadType, username: String) {
53 | self.event = event
54 | self.username = username
55 | super.init()
56 | }
57 |
58 | required init(from message: CommunicatorMessage) {
59 | event = message.payload
60 | username = message.peer.name
61 | super.init()
62 | }
63 | }
64 |
65 | class ChatRoom {
66 | fileprivate lazy var communicator: Communicator = {
67 | return Communicator(identifier: "marchthirteen", ownPeerID: self.ownName)
68 | }()
69 |
70 | fileprivate var typingEvents = [ChatEvent]() {
71 | didSet {
72 | typingStatusChanged?(typingEvents.count > 0)
73 | }
74 | }
75 |
76 | let ownName = UIDevice.current.name
77 | var messages = [ChatEvent]()
78 |
79 | var messagesChanged: (() -> Void)?
80 | var usersChanged: (([String]) -> Void)?
81 | var typingStatusChanged: ((Bool) -> Void)?
82 |
83 | init() {
84 | communicator.messageReceived = receive
85 | communicator.messageSent = receive
86 | communicator.peersChanged = peersChanged
87 | }
88 |
89 | func send(message: String) {
90 | communicator.send(message: .message(text: message))
91 | }
92 |
93 | func sendStartTyping() {
94 | communicator.send(message: .typingStarted)
95 | }
96 |
97 | func receive(message: ChatEvent) {
98 | switch message.event {
99 | case .typingStarted:
100 | let weAlreadyHaveThisEvent = typingEvents.filter {
101 | $0.username == message.username
102 | }.count > 0
103 | // Do not count our own typing events or already existing ones
104 | guard message.username != ownName,
105 | !weAlreadyHaveThisEvent else { return }
106 | typingEvents.append(message)
107 | case .message:
108 | // Only keep typing events where the username is not that of
109 | // the received message (assume that user has finished typing)
110 | typingEvents = typingEvents.filter {
111 | $0.username != message.username
112 | }
113 | messages.append(message)
114 | messagesChanged?()
115 | }
116 | }
117 |
118 | func peersChanged(peers: [Peer]) {
119 | let userList = peers.map { $0.name }
120 | usersChanged?(userList)
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/MarchThirteen/MarchThirteen/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // MarchThirteen
4 | //
5 | // Created by Luis Reisewitz on 13.03.17.
6 | // Copyright © 2017 ZweiGraf. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import PureLayout
11 | import Sensitive
12 | import JSQMessagesViewController
13 |
14 | class ViewController: JSQMessagesViewController {
15 | let startTime = mach_absolute_time()
16 | let incomingBubble = JSQMessagesBubbleImageFactory().incomingMessagesBubbleImage(with: UIColor(red: 10/255, green: 180/255, blue: 230/255, alpha: 1.0))
17 | let outgoingBubble = JSQMessagesBubbleImageFactory().outgoingMessagesBubbleImage(with: UIColor.lightGray)
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 |
22 | automaticallyScrollsToMostRecentMessage = true
23 | chatRoom.messagesChanged = messagesChanged
24 | chatRoom.typingStatusChanged = typingStatusChanged
25 | chatRoom.usersChanged = usersChanged
26 | }
27 |
28 | // MARK: Model
29 | let chatRoom = ChatRoom()
30 | }
31 |
32 | // MARK: - JSQMessagesViewController Overrides
33 | extension ViewController {
34 | override func senderId() -> String {
35 | return chatRoom.ownName
36 | }
37 |
38 | override func senderDisplayName() -> String {
39 | return chatRoom.ownName
40 | }
41 |
42 | override func didPressSend(_ button: UIButton, withMessageText text: String, senderId: String, senderDisplayName: String, date: Date) {
43 | chatRoom.send(message: text)
44 | finishSendingMessage(animated: true)
45 | }
46 |
47 | override func textViewDidChange(_ textView: UITextView) {
48 | super.textViewDidChange(textView)
49 | // Send start typing message when the textfield has one character
50 | guard textView.text?.characters.count == 1 else { return }
51 | chatRoom.sendStartTyping()
52 | }
53 |
54 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
55 | return chatRoom.messages.count
56 | }
57 |
58 | override func collectionView(_ collectionView: JSQMessagesCollectionView, messageDataForItemAt indexPath: IndexPath) -> JSQMessageData {
59 | let data = chatRoom.messages[indexPath.row]
60 | let text: String
61 | if case let .message(messageText) = data.event {
62 | text = messageText
63 | } else {
64 | text = ""
65 | }
66 | return JSQMessage(senderId: data.username, displayName: data.username, text: text)
67 | }
68 |
69 | override func collectionView(_ collectionView: JSQMessagesCollectionView, messageBubbleImageDataForItemAt indexPath: IndexPath) -> JSQMessageBubbleImageDataSource? {
70 | let data = self.collectionView(collectionView, messageDataForItemAt: indexPath)
71 | switch(data.senderId()) {
72 | case senderId():
73 | return self.outgoingBubble
74 | default:
75 | return self.incomingBubble
76 | }
77 | }
78 |
79 | override func collectionView(_ collectionView: JSQMessagesCollectionView, avatarImageDataForItemAt indexPath: IndexPath) -> JSQMessageAvatarImageDataSource? {
80 | let data = chatRoom.messages[indexPath.row]
81 | guard let image = UIImage.initialsImage(for: data.username) else {
82 | return nil
83 | }
84 | return JSQMessagesAvatarImage(avatarImage: image, highlightedImage: nil, placeholderImage: image)
85 | }
86 | }
87 |
88 | // MARK: - Chat Room Callbacks
89 | extension ViewController {
90 | func messagesChanged() {
91 | DispatchQueue.main.async {
92 | self.finishReceivingMessage(animated: true)
93 | }
94 | }
95 |
96 | func typingStatusChanged(shouldShowTypingIndicator: Bool) {
97 | DispatchQueue.main.async {
98 | self.showTypingIndicator = shouldShowTypingIndicator
99 | }
100 | }
101 |
102 | func usersChanged(users: [String]) {
103 | DispatchQueue.main.async {
104 | self.title = users.joined(separator: ", ")
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/MarchThirteen/MarchThirteen/WalkieTalkie/Service.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Service.swift
3 | // MarchThirteen
4 | //
5 | // Created by Luis Reisewitz on 14.03.17.
6 | // Copyright © 2017 ZweiGraf. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import MultipeerConnectivity
11 |
12 | enum ServiceError: Error {
13 | case noConnectedPeers
14 | }
15 |
16 | internal class Service: NSObject {
17 | // MARK: 🔓 Public Properties 🔓
18 |
19 | typealias PeersChangedCallback = (_ peers: [MCPeerID]) -> Void
20 | typealias DataReceivedCallback = (_ data: Data, _ peer: MCPeerID) -> Void
21 |
22 | /// This callback will be called when the list of connected peers changes.
23 | var peersChanged: PeersChangedCallback?
24 | /// This callback will be called when the session receives data from one of
25 | /// the connected peers.
26 | var dataReceived: DataReceivedCallback?
27 |
28 | /// Initializes the service manager with a given service type and peerID.
29 | ///
30 | /// - Parameters:
31 | /// - type: A MultiPeer service type. Service type must be a unique
32 | /// string, at most 15 characters long and can contain only ASCII lowercase
33 | /// letters, numbers and hyphens.
34 | /// - peerID: The user's own peer id to be shown to others.
35 | init(with type: String, as peerID: MCPeerID) {
36 | self.type = type
37 | self.ownPeer = peerID
38 | super.init()
39 | }
40 |
41 | // MARK: 🔒 Private Properties 🔒
42 |
43 | /// The type that is used for advertising and browsing the service.
44 | fileprivate let type: String
45 |
46 | /// The list of currently connected peers (peers in state MCSessionState.connected).
47 | fileprivate var connectedPeers: [MCPeerID] {
48 | return session.connectedPeers
49 | }
50 |
51 | /// The peer id of the local user.
52 | fileprivate let ownPeer: MCPeerID
53 |
54 | /// Session used for communicating with peers.
55 | fileprivate lazy var session: MCSession = {
56 | let session = MCSession(
57 | peer: self.ownPeer, securityIdentity: nil, encryptionPreference: .none)
58 | session.delegate = self
59 | return session
60 | }()
61 |
62 | /// Advertises our peer to others.
63 | fileprivate lazy var serviceAdvertiser: MCNearbyServiceAdvertiser = {
64 | let advertiser = MCNearbyServiceAdvertiser(
65 | peer: self.ownPeer, discoveryInfo: nil, serviceType: self.type)
66 | advertiser.delegate = self
67 | return advertiser
68 | }()
69 |
70 | /// Browses for other peers.
71 | fileprivate lazy var serviceBrowser: MCNearbyServiceBrowser = {
72 | let browser = MCNearbyServiceBrowser(
73 | peer: self.ownPeer, serviceType: self.type)
74 | browser.delegate = self
75 | return browser
76 | }()
77 |
78 | deinit {
79 | // Stop MultiPeer Stuff
80 | stop()
81 | }
82 |
83 | }
84 |
85 | // MARK: - 🔓 Public API 🔓
86 | extension Service {
87 | /// Starts advertising & browsing for our service.
88 | func start() {
89 | serviceAdvertiser.startAdvertisingPeer()
90 | serviceBrowser.startBrowsingForPeers()
91 | }
92 |
93 | /// Stops advertising & browsing for our service.
94 | func stop() {
95 | serviceAdvertiser.stopAdvertisingPeer()
96 | serviceBrowser.stopBrowsingForPeers()
97 | }
98 |
99 | /// Tries to send given data to all connected peers.
100 | ///
101 | /// - Parameter data: data to be sent to peers.
102 | func send(data: Data) throws {
103 | guard session.connectedPeers.count > 0 else {
104 | throw ServiceError.noConnectedPeers
105 | }
106 | try session.send(
107 | data, toPeers: session.connectedPeers, with: .reliable)
108 | }
109 | }
110 |
111 | // MARK: - 🔒 Private Delegate Processing 🔒
112 | fileprivate extension Service {
113 | func notifyReceive(data: Data, from peer: MCPeerID) {
114 | dataReceived?(data, peer)
115 | }
116 |
117 | func notifyPeersChanged(peers: [MCPeerID]) {
118 | peersChanged?(peers)
119 | }
120 | }
121 |
122 | // MARK: - MCNearbyServiceAdvertiserDelegate
123 | extension Service: MCNearbyServiceAdvertiserDelegate {
124 | func advertiser(
125 | _ advertiser: MCNearbyServiceAdvertiser,
126 | didReceiveInvitationFromPeer peerID: MCPeerID,
127 | withContext context: Data?,
128 | invitationHandler: @escaping (Bool, MCSession?) -> Void) {
129 |
130 | // Automatically accept all invitations that we get
131 | invitationHandler(true, session)
132 | }
133 |
134 | // NOOPs
135 |
136 | func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didNotStartAdvertisingPeer error: Error) {
137 | // TODO: Handle advertising start errors
138 | }
139 | }
140 |
141 | // MARK: - MCNearbyServiceBrowserDelegate
142 | extension Service: MCNearbyServiceBrowserDelegate {
143 | func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) {
144 | // Automatically invite all peers we find
145 | browser.invitePeer(peerID, to: session, withContext: nil, timeout: 10)
146 | }
147 |
148 | // NOOPs
149 |
150 | func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {
151 | // TODO: handle pending / invitable peers
152 | }
153 | func browser(_ browser: MCNearbyServiceBrowser, didNotStartBrowsingForPeers error: Error) {
154 | // TODO: Handle browsing start errors
155 | }
156 | }
157 |
158 | // MARK: - MCSessionDelegate
159 | extension Service: MCSessionDelegate {
160 | func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
161 | notifyPeersChanged(peers: connectedPeers)
162 | }
163 |
164 | func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
165 | notifyReceive(data: data, from: peerID)
166 | }
167 |
168 | // NOOPs
169 |
170 | func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {}
171 | func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) {}
172 | func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL, withError error: Error?) {}
173 | }
174 |
--------------------------------------------------------------------------------
/MarchThirteen/MarchThirteen.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | A517CD811E77555600F67CC0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A517CD801E77555600F67CC0 /* AppDelegate.swift */; };
11 | A517CD831E77555600F67CC0 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A517CD821E77555600F67CC0 /* ViewController.swift */; };
12 | A517CD881E77555600F67CC0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A517CD871E77555600F67CC0 /* Assets.xcassets */; };
13 | A517CD8B1E77555600F67CC0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A517CD891E77555600F67CC0 /* LaunchScreen.storyboard */; };
14 | A561081E1E80A84D00FB50C1 /* UIImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A561081D1E80A84D00FB50C1 /* UIImageExtension.swift */; };
15 | A5BD327E1E7D9B310071E63F /* ChatRoom.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BD327D1E7D9B310071E63F /* ChatRoom.swift */; };
16 | A5BD32891E7D9DB50071E63F /* Communicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BD32871E7D9DB50071E63F /* Communicator.swift */; };
17 | A5BD328A1E7D9DB50071E63F /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BD32881E7D9DB50071E63F /* Service.swift */; };
18 | A5BD328C1E7D9E490071E63F /* CommunicatorPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BD328B1E7D9E490071E63F /* CommunicatorPayload.swift */; };
19 | A5BD328E1E7D9F760071E63F /* Peer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BD328D1E7D9F760071E63F /* Peer.swift */; };
20 | A5BD32901E7D9FB20071E63F /* CommunicatorMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BD328F1E7D9FB20071E63F /* CommunicatorMessage.swift */; };
21 | A5BD32921E7DB6EC0071E63F /* CommunicatorOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BD32911E7DB6EC0071E63F /* CommunicatorOutput.swift */; };
22 | DBFB395BD78A6DE9EEBC9069 /* Pods_MarchThirteen.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C81374F6A4B9C409EB7E531 /* Pods_MarchThirteen.framework */; };
23 | /* End PBXBuildFile section */
24 |
25 | /* Begin PBXFileReference section */
26 | 28997B1D764EEC218D7A1857 /* Pods-MarchThirteen.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MarchThirteen.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-MarchThirteen/Pods-MarchThirteen.debug.xcconfig"; sourceTree = ""; };
27 | 8C81FE065521EC7EF892FD53 /* Pods-MarchThirteen.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MarchThirteen.release.xcconfig"; path = "../Pods/Target Support Files/Pods-MarchThirteen/Pods-MarchThirteen.release.xcconfig"; sourceTree = ""; };
28 | 9C81374F6A4B9C409EB7E531 /* Pods_MarchThirteen.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MarchThirteen.framework; sourceTree = BUILT_PRODUCTS_DIR; };
29 | A517CD7D1E77555600F67CC0 /* MarchThirteen.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MarchThirteen.app; sourceTree = BUILT_PRODUCTS_DIR; };
30 | A517CD801E77555600F67CC0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
31 | A517CD821E77555600F67CC0 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
32 | A517CD871E77555600F67CC0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
33 | A517CD8A1E77555600F67CC0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
34 | A517CD8C1E77555600F67CC0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
35 | A561081D1E80A84D00FB50C1 /* UIImageExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageExtension.swift; sourceTree = ""; };
36 | A5BD327D1E7D9B310071E63F /* ChatRoom.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatRoom.swift; sourceTree = ""; };
37 | A5BD32871E7D9DB50071E63F /* Communicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Communicator.swift; sourceTree = ""; };
38 | A5BD32881E7D9DB50071E63F /* Service.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Service.swift; sourceTree = ""; };
39 | A5BD328B1E7D9E490071E63F /* CommunicatorPayload.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommunicatorPayload.swift; sourceTree = ""; };
40 | A5BD328D1E7D9F760071E63F /* Peer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Peer.swift; sourceTree = ""; };
41 | A5BD328F1E7D9FB20071E63F /* CommunicatorMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommunicatorMessage.swift; sourceTree = ""; };
42 | A5BD32911E7DB6EC0071E63F /* CommunicatorOutput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommunicatorOutput.swift; sourceTree = ""; };
43 | /* End PBXFileReference section */
44 |
45 | /* Begin PBXFrameworksBuildPhase section */
46 | A517CD7A1E77555600F67CC0 /* Frameworks */ = {
47 | isa = PBXFrameworksBuildPhase;
48 | buildActionMask = 2147483647;
49 | files = (
50 | DBFB395BD78A6DE9EEBC9069 /* Pods_MarchThirteen.framework in Frameworks */,
51 | );
52 | runOnlyForDeploymentPostprocessing = 0;
53 | };
54 | /* End PBXFrameworksBuildPhase section */
55 |
56 | /* Begin PBXGroup section */
57 | 67EFB15B27D9DF441D71692E /* Pods */ = {
58 | isa = PBXGroup;
59 | children = (
60 | 28997B1D764EEC218D7A1857 /* Pods-MarchThirteen.debug.xcconfig */,
61 | 8C81FE065521EC7EF892FD53 /* Pods-MarchThirteen.release.xcconfig */,
62 | );
63 | name = Pods;
64 | sourceTree = "";
65 | };
66 | 8CE1B4671E21297CCEB772CD /* Frameworks */ = {
67 | isa = PBXGroup;
68 | children = (
69 | 9C81374F6A4B9C409EB7E531 /* Pods_MarchThirteen.framework */,
70 | );
71 | name = Frameworks;
72 | sourceTree = "";
73 | };
74 | A517CD741E77555600F67CC0 = {
75 | isa = PBXGroup;
76 | children = (
77 | A517CD7F1E77555600F67CC0 /* MarchThirteen */,
78 | A517CD7E1E77555600F67CC0 /* Products */,
79 | 67EFB15B27D9DF441D71692E /* Pods */,
80 | 8CE1B4671E21297CCEB772CD /* Frameworks */,
81 | );
82 | sourceTree = "";
83 | };
84 | A517CD7E1E77555600F67CC0 /* Products */ = {
85 | isa = PBXGroup;
86 | children = (
87 | A517CD7D1E77555600F67CC0 /* MarchThirteen.app */,
88 | );
89 | name = Products;
90 | sourceTree = "";
91 | };
92 | A517CD7F1E77555600F67CC0 /* MarchThirteen */ = {
93 | isa = PBXGroup;
94 | children = (
95 | A561081C1E80A81F00FB50C1 /* Utils */,
96 | A5BD32861E7D9DB50071E63F /* WalkieTalkie */,
97 | A517CD801E77555600F67CC0 /* AppDelegate.swift */,
98 | A517CD821E77555600F67CC0 /* ViewController.swift */,
99 | A517CD871E77555600F67CC0 /* Assets.xcassets */,
100 | A517CD891E77555600F67CC0 /* LaunchScreen.storyboard */,
101 | A517CD8C1E77555600F67CC0 /* Info.plist */,
102 | A5BD327D1E7D9B310071E63F /* ChatRoom.swift */,
103 | );
104 | path = MarchThirteen;
105 | sourceTree = "";
106 | };
107 | A561081C1E80A81F00FB50C1 /* Utils */ = {
108 | isa = PBXGroup;
109 | children = (
110 | A561081D1E80A84D00FB50C1 /* UIImageExtension.swift */,
111 | );
112 | path = Utils;
113 | sourceTree = "";
114 | };
115 | A5BD32861E7D9DB50071E63F /* WalkieTalkie */ = {
116 | isa = PBXGroup;
117 | children = (
118 | A5BD32871E7D9DB50071E63F /* Communicator.swift */,
119 | A5BD32881E7D9DB50071E63F /* Service.swift */,
120 | A5BD328B1E7D9E490071E63F /* CommunicatorPayload.swift */,
121 | A5BD328D1E7D9F760071E63F /* Peer.swift */,
122 | A5BD328F1E7D9FB20071E63F /* CommunicatorMessage.swift */,
123 | A5BD32911E7DB6EC0071E63F /* CommunicatorOutput.swift */,
124 | );
125 | path = WalkieTalkie;
126 | sourceTree = "";
127 | };
128 | /* End PBXGroup section */
129 |
130 | /* Begin PBXNativeTarget section */
131 | A517CD7C1E77555600F67CC0 /* MarchThirteen */ = {
132 | isa = PBXNativeTarget;
133 | buildConfigurationList = A517CD8F1E77555600F67CC0 /* Build configuration list for PBXNativeTarget "MarchThirteen" */;
134 | buildPhases = (
135 | D2CB2ABC38B0411185AC341C /* [CP] Check Pods Manifest.lock */,
136 | A517CD791E77555600F67CC0 /* Sources */,
137 | A517CD7A1E77555600F67CC0 /* Frameworks */,
138 | A517CD7B1E77555600F67CC0 /* Resources */,
139 | 2A98BBA1C8B33651DF36C794 /* [CP] Embed Pods Frameworks */,
140 | 35F214CE6C748DEBCC9E7DA3 /* [CP] Copy Pods Resources */,
141 | );
142 | buildRules = (
143 | );
144 | dependencies = (
145 | );
146 | name = MarchThirteen;
147 | productName = MarchThirteen;
148 | productReference = A517CD7D1E77555600F67CC0 /* MarchThirteen.app */;
149 | productType = "com.apple.product-type.application";
150 | };
151 | /* End PBXNativeTarget section */
152 |
153 | /* Begin PBXProject section */
154 | A517CD751E77555600F67CC0 /* Project object */ = {
155 | isa = PBXProject;
156 | attributes = {
157 | LastSwiftUpdateCheck = 0820;
158 | LastUpgradeCheck = 0820;
159 | ORGANIZATIONNAME = ZweiGraf;
160 | TargetAttributes = {
161 | A517CD7C1E77555600F67CC0 = {
162 | CreatedOnToolsVersion = 8.2.1;
163 | ProvisioningStyle = Automatic;
164 | };
165 | };
166 | };
167 | buildConfigurationList = A517CD781E77555600F67CC0 /* Build configuration list for PBXProject "MarchThirteen" */;
168 | compatibilityVersion = "Xcode 3.2";
169 | developmentRegion = English;
170 | hasScannedForEncodings = 0;
171 | knownRegions = (
172 | en,
173 | Base,
174 | );
175 | mainGroup = A517CD741E77555600F67CC0;
176 | productRefGroup = A517CD7E1E77555600F67CC0 /* Products */;
177 | projectDirPath = "";
178 | projectRoot = "";
179 | targets = (
180 | A517CD7C1E77555600F67CC0 /* MarchThirteen */,
181 | );
182 | };
183 | /* End PBXProject section */
184 |
185 | /* Begin PBXResourcesBuildPhase section */
186 | A517CD7B1E77555600F67CC0 /* Resources */ = {
187 | isa = PBXResourcesBuildPhase;
188 | buildActionMask = 2147483647;
189 | files = (
190 | A517CD8B1E77555600F67CC0 /* LaunchScreen.storyboard in Resources */,
191 | A517CD881E77555600F67CC0 /* Assets.xcassets in Resources */,
192 | );
193 | runOnlyForDeploymentPostprocessing = 0;
194 | };
195 | /* End PBXResourcesBuildPhase section */
196 |
197 | /* Begin PBXShellScriptBuildPhase section */
198 | 2A98BBA1C8B33651DF36C794 /* [CP] Embed Pods Frameworks */ = {
199 | isa = PBXShellScriptBuildPhase;
200 | buildActionMask = 2147483647;
201 | files = (
202 | );
203 | inputPaths = (
204 | );
205 | name = "[CP] Embed Pods Frameworks";
206 | outputPaths = (
207 | );
208 | runOnlyForDeploymentPostprocessing = 0;
209 | shellPath = /bin/sh;
210 | shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-MarchThirteen/Pods-MarchThirteen-frameworks.sh\"\n";
211 | showEnvVarsInLog = 0;
212 | };
213 | 35F214CE6C748DEBCC9E7DA3 /* [CP] Copy Pods Resources */ = {
214 | isa = PBXShellScriptBuildPhase;
215 | buildActionMask = 2147483647;
216 | files = (
217 | );
218 | inputPaths = (
219 | );
220 | name = "[CP] Copy Pods Resources";
221 | outputPaths = (
222 | );
223 | runOnlyForDeploymentPostprocessing = 0;
224 | shellPath = /bin/sh;
225 | shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-MarchThirteen/Pods-MarchThirteen-resources.sh\"\n";
226 | showEnvVarsInLog = 0;
227 | };
228 | D2CB2ABC38B0411185AC341C /* [CP] Check Pods Manifest.lock */ = {
229 | isa = PBXShellScriptBuildPhase;
230 | buildActionMask = 2147483647;
231 | files = (
232 | );
233 | inputPaths = (
234 | );
235 | name = "[CP] Check Pods Manifest.lock";
236 | outputPaths = (
237 | );
238 | runOnlyForDeploymentPostprocessing = 0;
239 | shellPath = /bin/sh;
240 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
241 | showEnvVarsInLog = 0;
242 | };
243 | /* End PBXShellScriptBuildPhase section */
244 |
245 | /* Begin PBXSourcesBuildPhase section */
246 | A517CD791E77555600F67CC0 /* Sources */ = {
247 | isa = PBXSourcesBuildPhase;
248 | buildActionMask = 2147483647;
249 | files = (
250 | A5BD328C1E7D9E490071E63F /* CommunicatorPayload.swift in Sources */,
251 | A517CD831E77555600F67CC0 /* ViewController.swift in Sources */,
252 | A5BD32901E7D9FB20071E63F /* CommunicatorMessage.swift in Sources */,
253 | A561081E1E80A84D00FB50C1 /* UIImageExtension.swift in Sources */,
254 | A517CD811E77555600F67CC0 /* AppDelegate.swift in Sources */,
255 | A5BD328A1E7D9DB50071E63F /* Service.swift in Sources */,
256 | A5BD32891E7D9DB50071E63F /* Communicator.swift in Sources */,
257 | A5BD327E1E7D9B310071E63F /* ChatRoom.swift in Sources */,
258 | A5BD328E1E7D9F760071E63F /* Peer.swift in Sources */,
259 | A5BD32921E7DB6EC0071E63F /* CommunicatorOutput.swift in Sources */,
260 | );
261 | runOnlyForDeploymentPostprocessing = 0;
262 | };
263 | /* End PBXSourcesBuildPhase section */
264 |
265 | /* Begin PBXVariantGroup section */
266 | A517CD891E77555600F67CC0 /* LaunchScreen.storyboard */ = {
267 | isa = PBXVariantGroup;
268 | children = (
269 | A517CD8A1E77555600F67CC0 /* Base */,
270 | );
271 | name = LaunchScreen.storyboard;
272 | sourceTree = "";
273 | };
274 | /* End PBXVariantGroup section */
275 |
276 | /* Begin XCBuildConfiguration section */
277 | A517CD8D1E77555600F67CC0 /* Debug */ = {
278 | isa = XCBuildConfiguration;
279 | buildSettings = {
280 | ALWAYS_SEARCH_USER_PATHS = NO;
281 | CLANG_ANALYZER_NONNULL = YES;
282 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
283 | CLANG_CXX_LIBRARY = "libc++";
284 | CLANG_ENABLE_MODULES = YES;
285 | CLANG_ENABLE_OBJC_ARC = YES;
286 | CLANG_WARN_BOOL_CONVERSION = YES;
287 | CLANG_WARN_CONSTANT_CONVERSION = YES;
288 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
289 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
290 | CLANG_WARN_EMPTY_BODY = YES;
291 | CLANG_WARN_ENUM_CONVERSION = YES;
292 | CLANG_WARN_INFINITE_RECURSION = YES;
293 | CLANG_WARN_INT_CONVERSION = YES;
294 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
295 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
296 | CLANG_WARN_UNREACHABLE_CODE = YES;
297 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
298 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
299 | COPY_PHASE_STRIP = NO;
300 | DEBUG_INFORMATION_FORMAT = dwarf;
301 | ENABLE_STRICT_OBJC_MSGSEND = YES;
302 | ENABLE_TESTABILITY = YES;
303 | GCC_C_LANGUAGE_STANDARD = gnu99;
304 | GCC_DYNAMIC_NO_PIC = NO;
305 | GCC_NO_COMMON_BLOCKS = YES;
306 | GCC_OPTIMIZATION_LEVEL = 0;
307 | GCC_PREPROCESSOR_DEFINITIONS = (
308 | "DEBUG=1",
309 | "$(inherited)",
310 | );
311 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
312 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
313 | GCC_WARN_UNDECLARED_SELECTOR = YES;
314 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
315 | GCC_WARN_UNUSED_FUNCTION = YES;
316 | GCC_WARN_UNUSED_VARIABLE = YES;
317 | IPHONEOS_DEPLOYMENT_TARGET = 10.2;
318 | MTL_ENABLE_DEBUG_INFO = YES;
319 | ONLY_ACTIVE_ARCH = YES;
320 | SDKROOT = iphoneos;
321 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
322 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
323 | TARGETED_DEVICE_FAMILY = "1,2";
324 | };
325 | name = Debug;
326 | };
327 | A517CD8E1E77555600F67CC0 /* Release */ = {
328 | isa = XCBuildConfiguration;
329 | buildSettings = {
330 | ALWAYS_SEARCH_USER_PATHS = NO;
331 | CLANG_ANALYZER_NONNULL = YES;
332 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
333 | CLANG_CXX_LIBRARY = "libc++";
334 | CLANG_ENABLE_MODULES = YES;
335 | CLANG_ENABLE_OBJC_ARC = YES;
336 | CLANG_WARN_BOOL_CONVERSION = YES;
337 | CLANG_WARN_CONSTANT_CONVERSION = YES;
338 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
339 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
340 | CLANG_WARN_EMPTY_BODY = YES;
341 | CLANG_WARN_ENUM_CONVERSION = YES;
342 | CLANG_WARN_INFINITE_RECURSION = YES;
343 | CLANG_WARN_INT_CONVERSION = YES;
344 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
345 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
346 | CLANG_WARN_UNREACHABLE_CODE = YES;
347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
348 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
349 | COPY_PHASE_STRIP = NO;
350 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
351 | ENABLE_NS_ASSERTIONS = NO;
352 | ENABLE_STRICT_OBJC_MSGSEND = YES;
353 | GCC_C_LANGUAGE_STANDARD = gnu99;
354 | GCC_NO_COMMON_BLOCKS = YES;
355 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
356 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
357 | GCC_WARN_UNDECLARED_SELECTOR = YES;
358 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
359 | GCC_WARN_UNUSED_FUNCTION = YES;
360 | GCC_WARN_UNUSED_VARIABLE = YES;
361 | IPHONEOS_DEPLOYMENT_TARGET = 10.2;
362 | MTL_ENABLE_DEBUG_INFO = NO;
363 | SDKROOT = iphoneos;
364 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
365 | TARGETED_DEVICE_FAMILY = "1,2";
366 | VALIDATE_PRODUCT = YES;
367 | };
368 | name = Release;
369 | };
370 | A517CD901E77555600F67CC0 /* Debug */ = {
371 | isa = XCBuildConfiguration;
372 | baseConfigurationReference = 28997B1D764EEC218D7A1857 /* Pods-MarchThirteen.debug.xcconfig */;
373 | buildSettings = {
374 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
375 | DEVELOPMENT_TEAM = "";
376 | INFOPLIST_FILE = MarchThirteen/Info.plist;
377 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
378 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
379 | PRODUCT_BUNDLE_IDENTIFIER = com.zweigraf.MarchThirteen;
380 | PRODUCT_NAME = "$(TARGET_NAME)";
381 | SWIFT_VERSION = 3.0;
382 | };
383 | name = Debug;
384 | };
385 | A517CD911E77555600F67CC0 /* Release */ = {
386 | isa = XCBuildConfiguration;
387 | baseConfigurationReference = 8C81FE065521EC7EF892FD53 /* Pods-MarchThirteen.release.xcconfig */;
388 | buildSettings = {
389 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
390 | DEVELOPMENT_TEAM = "";
391 | INFOPLIST_FILE = MarchThirteen/Info.plist;
392 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
393 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
394 | PRODUCT_BUNDLE_IDENTIFIER = com.zweigraf.MarchThirteen;
395 | PRODUCT_NAME = "$(TARGET_NAME)";
396 | SWIFT_VERSION = 3.0;
397 | };
398 | name = Release;
399 | };
400 | /* End XCBuildConfiguration section */
401 |
402 | /* Begin XCConfigurationList section */
403 | A517CD781E77555600F67CC0 /* Build configuration list for PBXProject "MarchThirteen" */ = {
404 | isa = XCConfigurationList;
405 | buildConfigurations = (
406 | A517CD8D1E77555600F67CC0 /* Debug */,
407 | A517CD8E1E77555600F67CC0 /* Release */,
408 | );
409 | defaultConfigurationIsVisible = 0;
410 | defaultConfigurationName = Release;
411 | };
412 | A517CD8F1E77555600F67CC0 /* Build configuration list for PBXNativeTarget "MarchThirteen" */ = {
413 | isa = XCConfigurationList;
414 | buildConfigurations = (
415 | A517CD901E77555600F67CC0 /* Debug */,
416 | A517CD911E77555600F67CC0 /* Release */,
417 | );
418 | defaultConfigurationIsVisible = 0;
419 | defaultConfigurationName = Release;
420 | };
421 | /* End XCConfigurationList section */
422 | };
423 | rootObject = A517CD751E77555600F67CC0 /* Project object */;
424 | }
425 |
--------------------------------------------------------------------------------