Using Bonjour in Swift

Peter Wood in Swift
14 Oct 2014, 12:26

This article provides easy step-by-step instructions allowing to create your first app in Swift. You’ll create an application for searching the network for computers with enabled file sharing. With the help of this app, users will be able to search the network for computers which use AFP or SMB protocols for files and folders sharing, and connect to any of them.

Note: You’ll need the latest version of Xcode 6.1.

To create a Swift project in Xcode:

  1. Start Xcode.
  2. Choose File > New > Project > OS X > Application > Cocoa Application.
  3. Enter SharingBrowser into Product Name field and choose Swift from the Language drop-down list. Make sure Use Core Data checkbox is disabled, and click Next.
  4. Select the folder to save the project to, and click Create.

tumblr_inline_ndfunc2mhU1sit1v2

A Swift project’s structure is nearly identical to an Objective-C project, with one important distinction: Swift has no header files. There is no explicit delineation between the implementation and the interface, so all the information about a particular class resides in a single .swift file.

tumblr_inline_ndfumyRDAg1sit1v2

Adding Classes to the Project

SharedPoint class

Create a new Swift class named SharedPoint. To do this, choose from the menu: File > New > File > OS X > Source > Swift. This class is designed to describe the compliance between file sharing service and its IP address.

SharedPoint.swift file contains a string with import statement:

import Foundation  

This string is intended to import Objective-C Foundation system framework into the Swift project. Importing makes all API of the framework available in Swift.

Add the SharedPoint class definition to the file. Class definition starts with the key word class and is enclosed in curly brackets:

    class SharedPoint {          // class definition goes here      }  

Add three properties to the class:

  • netService – variable of NSNetService type – file sharing service on computer.
  • type – variable of String type – file sharing protocol (SMB or AFP).
  • ipAddress – variable of String type – computer’s IP address in the network.
    class SharedPoint {          var netService:NSNetService          var type:String          var ipAddress:String          }  

If app is launched right away, the following compilation error is invoked:

Class ‘SharedPoint’ has no initializers.

This happens because Swift requires all class properties, the types of which are optional, to be initialized at class instantiation.

Add initializer with three named parameters to the class:

    init(netService:NSNetService, type:String, ipAddress:String) {          self.netService = netService          self.type = type          self.ipAddress = ipAddress          }  

With this initializer you can create an instance of the SharedPoint class, passing named values to each of its parameters. Note that one cannot call the initializer without stating external parameters names as it will lead to error.

Add the Equatable protocol conformance to the SharedPoint class. It allows to define whether two values of one and the same type are identical. To do this, add the name of Equatable protocol to the SharedPoint class definition after the class name separated by a colon:

    class SharedPoint:Equatable {          // class definition goes here      }  

Then add the following function at global scope:

    func == (lhs: SharedPoint, rhs: SharedPoint) -> Bool {          return (lhs.netService == rhs.netService) &&                    (lhs.type == rhs.type) &&                    (lhs.ipAddress == rhs.ipAddress)      }  

SharedBrowser class

Create a new Swift class named SharedBrowser. The class is intended to search computers with enabled file sharing in the network. To implement the search option, we’ll use NSNetServiceBrowser class, which defines an interface for finding published services on a network using multicast DNS. Due to the possible delays associated with network traffic, NSNetServiceBrowser objects perform browsing asynchronously by registering with the default run loop. Browsing results are returned to your app through delegate methods. To handle results from an NSNetServiceBrowser object, you must assign it a delegate.

Thus, to perform search, do the following:

  1. Initialize an NSNetServiceBrowser instance and assign a delegate to the object.
  2. Start the search.
  3. Handle search results and other messages sent to delegate.

Each browser service performs one search operation at a time, so to perform multiple operations simultaneously, use several browser services. We’ll need two browser services: the first one – to search the network for computers which use SMB protocol for files sharing, the second one – to search the network for computers which use AFP protocol.

From the NSNetServiceBrowserDelegate protocol declaration, we see that it requires a conforming type also to conform to the NSObjectProtocol protocol:

    protocol NSNetServiceBrowserDelegate : NSObjectProtocol {      	// protocol definition goes here      }  

Since NSObject class conforms to the NSObjectProtocol protocol:

    class NSObject : NSObjectProtocol {          // class definition goes here      }  

we’ll make it superclass for the SharedBrowser class:

    class SharedBrowser:NSObject {          // class definition goes here      }  

Add the following properties to SharedBrowser class:

  • afpType – constant of String type – registration name of AFP service, which works over TCP protocol (see Bonjour Names for Existing Service Types);
  • smbType – constant of String type – registration name of SMB service;
  • smbBrowser – constant of NSNetServiceBrowser type – browser for computers in the network that provide access to files and folders over SMB protocol;
  • afpBrowser – variable of NSNetServiceBrowser type – browser for computers in the network that provide access to files and folders over AFP protocol;
  • serviceList – the list of detected services (array of objects of NSNetService type);
  • sharedPointsList – array of objects of SharedPoint type, each of which can be used for connecting to a certain computer in the network with enabled sharing via the IP address over the supported sharing protocol.
    class SharedBrowser:NSObject {          let afpType:String          let smbType:String          var smbBrowser:NSNetServiceBrowser          var afpBrowser:NSNetServiceBrowser          var serviceList:[NSNetService]          var sharedPointsList:[SharedPoint]          }  

To add initializer init() to SharedBrowser class, it’s necessary to override the designated NSObject initializer of its superclass via the key word override.

     override init() {      	self.afpType = "_afpovertcp._tcp."      	self.smbType = "_smb._tcp."      	self.smbBrowser = NSNetServiceBrowser()      	self.afpBrowser = NSNetServiceBrowser()      	self.serviceList = [NSNetService]()      	self.sharedPointsList = [SharedPoint]()    	super.init()      	self.afpBrowser.delegate = self      	self.smbBrowser.delegate = self  	}  

Initializer first assigns values to class properties, then it delegates up to init() initializer of the NSObject class. Then the fully initialized object assigns itself as delegate for objects afpBrowser and smbBrowser.

Please pay attention that calling the superclass’s initializer init() precedes the setting of delegates. Otherwise we’ll get the compilation error:

‘self’ used before super.init call.

Delegate of the NSNetServiceBrowser object must conform to the NSNetServiceBrowserDelegate protocol. To avoid the following compilation error:

Type ‘SharedBrowser’ does not conform to protocol ’NSNetServiceBrowserDelegate’,

add the name of the NSNetServiceBrowserDelegate protocol to the SharedBrowser class header after the name of its superclass, separated by comma:

    class SharedBrowser:NSObject, NSNetServiceBrowserDelegate {          //properties and methods      }  

The NSNetServiceBrowserDelegate protocol defines the optional methods implemented by delegates of NSNetServiceBrowser objects.

Let’s study some of them:

netServiceBrowser(_,didFindService:,moreComing:) tells the delegate the browser found a service.

Method declaration:

optional func netServiceBrowser(_ netServiceBrowser: NSNetServiceBrowser,                                                   didFindService netService: NSNetService,                                                   moreComing moreServicesComing: Bool)  

netService parameter of NSNetService type is the detected service.

moreServicesComing parameter of Bool type shows whether the browser expects any other additional services.

Add implementation of this method to SharedBrowser class. When calling this method, the detected service is to be added to serviceList array, and when the service search is over, update() method, which description is provided below, is to be called:

     func netServiceBrowser(aNetServiceBrowser: NSNetServiceBrowser,                                         didFindService aNetService: NSNetService,                                         moreComing: Bool) {      	serviceList.append(aNetService)      	NSLog("Found: \(aNetService)")      	if !moreComing {              update()      	}      }  

netServiceBrowser(_,didRemoveService:,moreComing:) method tells the delegate a service has disappeared or has become unavailable.

Method declaration:

optional func netServiceBrowser(_ netServiceBrowser: NSNetServiceBrowser,                                                 didRemoveService netService: NSNetService,                                                 moreComing moreServicesComing: Bool)  

When calling this method, the service which became unavailable is to be deleted from serviceList array, and its corresponding elements should be deleted from sharedPointsList array. When the service search is over, update() method should be called.

    func netServiceBrowser(aNetServiceBrowser: NSNetServiceBrowser,                                         didRemoveService aNetService: NSNetService,                                         moreComing: Bool) {          let iService = find(serviceList, aNetService)          if iService != nil {              serviceList.removeAtIndex(iService!)          }          let subArray = sharedPointsList.filter({$0.netService == aNetService})          for item in subArray {              let iSharedPoint = find(sharedPointsList, item)              if iSharedPoint != nil {                  sharedPointsList.removeAtIndex(iSharedPoint!)              }          }          NSLog("Became unavailable: \(aNetService)")          if !moreComing {              update()          }      }  

find method returns the first index where `value` appears in `domain` or `nil` if `value` is not found. Elements must conform to the Equatable protocol.

filter method returns an array containing the elements of the receiver for which a provided closure indicates a match. In our case it’s an array of elements of [SharedPoint] type, in which netService variable refers to the service which became unavailable.

netServiceBrowser(_,didNotSearch:) method tells the delegate that the search failed.

Method declaration:

optional func netServiceBrowser(_ netServiceBrowser: NSNetServiceBrowser,                                                 didNotSearch errorDict: [NSObject : AnyObject])  

When calling this method, the message containing the error code will be displayed in console. One can get the error code via the NSNetServiceErrorCode key in errorDict dictionary.

    func netServiceBrowser(aNetServiceBrowser: NSNetServiceBrowser,                                         didNotSearch errorDict: [NSObject : AnyObject]) {          NSLog("Search was not successful. Error code: \(errorDict[NSNetServicesErrorCode]!)")          }  

In update() method, for each service of serviceList array, we’ll set object of SharedBrowser class as delegate, and start the process of service address resolution.

    func update() {      	for service in serviceList {          	    service.delegate = self          	    service.resolveWithTimeout(5)      	}      }  

NSNetService object delegate must conform to the NSNetServiceDelegate protocol. The name of the NSNetServiceDelegate protocol should be added to the SharedBrowser class header.

    class SharedBrowser:NSObject, NSNetServiceBrowserDelegate, NSNetServiceDelegate {          //properties and methods      }  

The NSNetServiceDelegate protocol defines the optional methods implemented by delegates of the NSNetService objects.

We’ll add this protocol conformance to the SharedBrowser class.

netServiceDidResolveAddress(_) method informs the delegate that the address for a given service was resolved.

optional func netServiceDidResolveAddress(_ sender: NSNetService)  

Let’s study in detail the method implementation:

    func netServiceDidResolveAddress(sender: NSNetService) {          for addressBytes in sender.addresses! {              var inetAddress : sockaddr_in!              var inetAddress6 : sockaddr_in6!          	  //NSData’s bytes returns a read-only pointer to the receiver’s contents.          	  var inetAddressPointer = UnsafePointer(addressBytes.bytes)          	  //Access the underlying raw memory              inetAddress = inetAddressPointer.memory              if inetAddress.sin_family == __uint8_t(AF_INET) {              }              else {                  if inetAddress.sin_family == __uint8_t(AF_INET6) {                      var inetAddressPointer6 = UnsafePointer(addressBytes.bytes)                      inetAddress6 = inetAddressPointer6.memory                      inetAddress = nil                  }                  else {                      inetAddress = nil                  }              }              var ipString : UnsafePointer?          	  //static func alloc(num: Int) -> UnsafeMutablePointer              var ipStringBuffer = UnsafeMutablePointer.alloc(Int(INET6_ADDRSTRLEN))              if inetAddress != nil {                  var addr = inetAddress.sin_addr                  ipString = inet_ntop(Int32(inetAddress.sin_family),                                            	&addr,                                            	ipStringBuffer,                                            	__uint32_t (INET6_ADDRSTRLEN))              } else {                  if inetAddress6 != nil {                      var addr = inetAddress6.sin6_addr                      ipString = inet_ntop(Int32(inetAddress6.sin6_family),                                                    &addr,                                                    ipStringBuffer,                                                    __uint32_t(INET6_ADDRSTRLEN))                  }                }              if ipString != nil {                  // Returns `nil` if the `CString` is `NULL` or if it contains ill-formed                  // UTF-8 code unit sequences.                  var ip = String.fromCString(ipString)                  if ip != nil {                      NSLog("\(sender.name)(\(sender.type)) - \(ip!)")                      var ext : String                      switch sender.type {                      case afpType :                      ext = "afp"                      case smbType :                      ext = "smb"                      default:                      ext = sender.type                      }                      sharedPointsList.append(SharedPoint(netService:sender, type:ext, ipAddress:ip!))                      NSNotificationCenter.defaultCenter().postNotificationName(                               	                                          "NewSharedDetected", object: self)                  }              }              ipStringBuffer.dealloc(Int(INET6_ADDRSTRLEN))          }      }  

addresses property of NSNetService class returns an array of objects of [AnyObject] type, each of which contains the sockaddr structure, which can be used to connect to socket:

var addresses: [AnyObject]? { get }  

When working with Cocoa APIs, it is common to receive an array with a type of [AnyObject], or “an array of values of any object type”. This is because Objective-C does not have explicitly typed arrays. Though, in the most of cases you can be sure of the type of objects contained in such an array, basing on the information about the API provided by the array.

For example, in our case it is known that the type of objects in array returned by addresses property of NSNetService class is NSData.

Enumerate all elements of sender.addresses array (sender constant of NSNetService type is a service for which addresses are resolved) and perform the following:

1. Declare two variables: inetAddress of sockaddr_in! type and inetAddress6 of sockaddr_in6! type for IPv4 and IPv6 addresses correspondingly. Both variables are implicitly unwrapped optionals.

sockaddr_in structure describes the socket for work with TCP/IP protocols and has the following form:

    struct sockaddr_in {          var sin_len: __uint8_t          var sin_family: sa_family_t          var sin_port: in_port_t          var sin_addr: in_addr          var sin_zero: (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)      }  

The structure properties:

  • sin_len – structure length;
  • sin_family – address family (must be equal to AF_INET);
  • sin_port – port of IP addresses;
  • sin_addr – IP address;
  • sin_zero – padding.

For IPv6 address representation, sockaddr_in6 structure is used. sin6_family property stores address family (must be equal to AF_INET6), and sin6_addr property stores IPv6 address.

2. Declare inetAddressPointer variable and assign constant pointer to sockaddr_in structure to it.
bytes property of NSData class returns pointer of UnsafePointer<()> type (analog in Objective-C is const void *) to data of object of this class:

var bytes: UnsafePointer<()> { get }  

As it is known that the object contains sockaddr structure, we can cast returned value to UnsafePointer type.

3. Assign to inetAddress variable the value which is stored in inetAddressPointer pointer.

To get the object stored in UnsafePointer structure, we’ll use its memory property:

var memory: T { get }  

4. Check address family. Note that during address family check implicit type casting is carried out (AF_INET – Int32 type, while the type of sin_family property of sockaddr_in structure is sa_family_t (alias for __uint8_t type). Omitting type casting leads to compilation error.

In case the address family is AF_INET (IPv4 address), no additional actions are required. Otherwise, check whether AF_INET6 (IPv6) is the address family. If it really is, then assign constant pointer to sockaddr_in6 structure to inetAddressPointer6 variable, the structure stored in this address to inetAddress6 variable, and nil to inetAddress variable.If not, assign nil to inetAddress variable.

5. Declare ipString variable of UnsafePointer? optional type. Question mark means that the variable value is optional, that is, the variable can contain constant pointer to Int8 type or can contain no values at all.

6. Declare ipStringBuffer variable of UnsafeMutablePointer type (pointer to Int8 type) and allocate memory via alloc method.

7. As there is a possibility that inetAddress variable became equal to nil, process it as optional variable to check whether it contains any value. If it does, declare addr variable and assign to it the address stored in sin_addr field of inetAddress structure. Then assign constant pointer to the string returned by inet_ntop function to ipString variable.

inet_ntop function converts a packed Internet address into the readable format. The function gets binary IP address and returns the pointer to the string which contains decimal format with dots and colons.

inet_ntop function declaration in Swift:

func inet_ntop(_: Int32, _: UnsafePointer, _: UnsafeMutablePointer, _: socklen_t) -> UnsafePointer  

Function parameters:

  • variable of Int32 type – address family (sin_family variable of inetAddress structure should be casted to Int32 type, that is Int32(inetAddress.sin_family));
  • constant pointer of UnsafePointer type. Here’s an extract from Apple’s doc “Using Swift with Cocoa and Objective-C. Interacting with C APIs”:

When a function is declared as taking a UnsafePointer argument, it can accept the same operands as UnsafePointer for any type Type.

That’s why, we can pass the value of UnsafePointer type to the function as the second argument, that is, constant pointer to in_addr structure which stores IP address.

  • variable of UnsafeMutablePointer type – pointer to buffer;
  • variable of socklen_t type – buffer size (cast INET6_ADDRSTRLEN constant of Int32 type to __uint32_t type (socklen_t type is alias for __uint32_t)).

Underscore (_) means that you don’t have to indicate outer names for the parameters at function call.

In case inetAddress variable does not contain value, check whether inetAddress6 variable contains any value. If it does, declare addr variable and assign to it the address which is stored in sin6_addr field of inetAddress6 structure. Then assign constant pointer to the string returned by inet_ntop function to ipString variable.

8. Then check whether variable ipString contains value. If it does, declare ip variable and assign it the value returned by fromCString static method of String structure.

Since ipString variable is of UnsafePointer? optional type, one needs to force unpack it (exclamation mark after the variable name) before passing it to fromCString method. Casting UnsafePointer type to UnsafePointer type is not necessary, as CChar is alias for Int8.

If ip variable contains the value, declare ext variable of String type and assign to it the value depending on the service type. Note that in switch-case construction there is no break operator in case-blocks, as, unlike switches in C and Objective-C, switches in Swift do not pass to the next block by default after executing code inside the corresponding case-block.

Add to sharedPointsList array the object of SharedPoint class, which is created via initializer with three named parameters. As ip variable is of String? type, that is, optional String, we’ll force unpack it.

After that, send to the notification center of the process the notification that a new point with file sharing was found.

9. Since UnsafePointer structure does not provide automatic memory control, free the memory allocated to ipStringBuffer variable.

Add implementation of one more method of the NSNetServiceDelegate protocol to the SharedBrowser class.

netServiceDidResolveAddress(_,didNotResolve:) method informs the delegate that an error occurred during resolution of a given service.

Method declaration:

optional func netService(_ sender: NSNetService, didNotResolve errorDict: [NSObject : AnyObject])  

When calling this method, a message with code error will be shown in console. One can get an error code via NSNetServiceErrorCode key from errorDict dictionary.

     func netService(sender: NSNetService, didNotResolve errorDict: [NSObject : AnyObject]) {      	NSLog("\(sender.name) did not resolve: \(errorDict[NSNetServicesErrorCode]!)")      }   

All is left is to add to the class SharedBrowser method which is intended to launch network search for computers with enabled file sharing. In our case, there are three search variants available:

  1. search for computers with file sharing via AFP protocol;
  2. search for computers with file sharing via SMB protocol;
  3. search for computers with file sharing via AFP and SMB protocols.

To differentiate the three search variants, we’ll create ServiceType enumeration with three members, each of which corresponds to the definite search variant:

    enum ServiceType {  	case Afp  	case Smb  	case AfpAndSmb      }  

Note that, unlike in C and Objective-C, integer values cannot be assigned by default to enumeration members at their creation in Swift. Afp, Smb and AfpAndSmb members in ServiceType enumeration are not equal to 0, 1 and 2 correspondingly, but they are actual values.

Add the following method to SharedBrowser class:

    func searchForServicesOfType(type:ServiceType) {          switch type {          case .Afp:              afpBrowser.searchForServicesOfType(afpType, inDomain: "")          case .Smb:              smbBrowser.searchForServicesOfType(smbType, inDomain: "")          case .AfpAndSmb:              afpBrowser.searchForServicesOfType(afpType, inDomain: "")              smbBrowser.searchForServicesOfType(smbType, inDomain: "")          }      }  

The method accepts the only parameter of ServiceType type, describing the search variant. In switch-case construction, the short form of access to ServiceType enumeration members is used, like .Afp, not ServiceType.Afp, since it is known that the variable type is ServiceType.

searchForServicesOfType(_,inDomain:) method of NSNetServiceBrowser class launches the search for services of the definite type in the specified domain.

Method declaration:

func searchForServicesOfType(_ serviceType: String, inDomain domainName: String)  

Passing an empty string to the method as the second parameter lets the browser search for services among registration domains used by default.

Add to SharedBrowser class the last method which is intended to stop the current search and clear its results:

    func reset() {          smbBrowser.stop()          afpBrowser.stop()          for service in serviceList {              service.stop()          }          serviceList.removeAll()          sharedPointsList.removeAll()      }  

Now let’s start the creation of the user interface.

User Interface Creation

1. Choose MainMenu.xib file in project browser to display SharingBrowser window.

2. Display the Utilities in the right corner of the window. To do this, click the rightmost button of View switch on the toolbar.

tumblr_inline_ndfutdOOd41sit1v2

3. Display the main window. To do this, click Window icon in Editor.

tumblr_inline_ndfuspbLGQ1sit1v2

4. Open the Object Library. The Object Library can be found in the lower part of the Utilities.

tumblr_inline_ndfuser3WX1sit1v2

5. Drag Push Button, Table View, two Check Boxes and Box to the window from the Object Library.

6. Place the objects as shown on the picture and name them appropriately.

tumblr_inline_ndfuojWLlG1sit1v2

7. In Attributes inspector tab, choose Content Mode in Cell Based, and set the number of Columns to 4.

tumblr_inline_ndfurqy1zh1sit1v2

8. For each column of the table set the name in Attributes inspector tab. In Identity inspector tab, set the Restoration ID: Name and name correspondingly for the 1rst column, Domain and domain – for the 2nd column, Type and type – for the 3rd column, IP and ip – for the 4th column.

tumblr_inline_ndfurfEgEf1sit1v2 tumblr_inline_ndfur5h17e1sit1v2

9. Create outlet for each element in AppDelegate class. To do this, display Assistant editor panel by clicking Editor switch button tumblr_inline_ndfuqvUiYx1sit1v2 on the toolbar. Make sure that Assistant editor displays AppDelegate.swift file. If not, choose AppDelegate.swift file from Top Level Objects menu in the upper part of the panel.

tumblr_inline_ndfuqhHyON1sit1v2

For each element, do the following:

  • Click Control key and hold it, while dragging element to AppDelegate.swift file;
  • Release Control key and Assistant editor will display connection setup window;
  • Choose Outlet from Connection menu;
  • Input the name and click Connect button.

tumblr_inline_ndfuph6NcJ1sit1v2

Interface Builder will add the outlet declaration to the class. Outlets in Swift are declared via the @IBOutlet key word.

The following strings should appear in AppDelegate class:

    @IBOutlet weak var window: NSWindow!      @IBOutlet weak var sharedPointsTable: NSTableView!      @IBOutlet weak var afpCheckBox: NSButton!      @IBOutlet weak var smbCheckBox: NSButton!      @IBOutlet weak var searchBtn: NSButton!  

10. Set AppDelegate object as the data source for the table. To do this, click Control key and hold it, while dragging the pointer from the table towards AppDelegate object in the objects hierarchy.
Choose dataSource property.

11. Add action for Search button. Click Control key and hold it, while dragging Search button into AppDelegate.swift file. Release Control key, choose Action from Connection menu, input searchBtnAction as the action name. Interface Builder will add the stub for the method to AppDelegate class:

    @IBAction func searchBtnAction(sender: AnyObject) {      }  

Code Adding

Go to AppDelegate.swift file.

Add sharedBrowser property and initialize it by the instance of the SharedBrowser class:

    let sharedBrowser = SharedBrowser()  

Add the following code to the button click handler of Search button:

    @IBAction func searchBtnAction(sender: AnyObject) {          var searchType:ServiceType?          if afpCheckBox.state == NSOnState {              if smbCheckBox.state == NSOnState {              	searchType = .AfpAndSmb              } else {              	searchType = .Afp              }          } else if smbCheckBox.state == NSOnState {              searchType = .Smb          }                   if searchType != nil {              sharedBrowser.reset()              sharedPointsTable.reloadData()              sharedBrowser.searchForServicesOfType(searchType!);          } else {              var alert = NSAlert()              alert.messageText = "Choose protocol!"              alert.alertStyle = .WarningAlertStyle              alert.runModal()          }      }  

First, declare searchType variable of ServiceType? type, that is, optional ServiceType type. Make sure that afpCheckBox checkbox is ticked. If it is, make sure that smbCheckBox checkbox is ticked as well. If yes, assign .AfpAndSmb value to searchType, otherwise – .Afp.

If afpCheckBox checkbox is not ticked, make sure that smbCheckBox checkbox is ticked. If yes, assign .Smb value to searchType. Check whether searchType variable contains value. If it does, stop the previous search, clear its results and srart the new search. If not, display the warning window.

To populate the table with content programmatically, NSTableViewDataSource protocol should be realized. Since AppDelegate class was set in Interface Builder as the data source for the table, add the NSTableViewDataSource protocol conformance to it:

    class AppDelegate: NSObject, NSApplicationDelegate, NSTableViewDataSource {          // class definition goes here      }  

numberOfRowsInTableView(_:) method of NSTableViewDataSource protocol returns the number of records provided to NSTableView object by the data source.

Method declaration:

optional func numberOfRowsInTableView(_ aTableView: NSTableView) -> Int  

Add this method implementation to the AppDelegate class:

    func numberOfRowsInTableView(tableView: NSTableView!) -> Int {          return sharedBrowser.sharedPointsList.count      }  

tableView(_:objectValueForTableColumn:row:) method of the NSTableViewDataSource protocol is called by the table to return the data of the object, related to the indicated row and column.

Method declaration:

optional func tableView(_ aTableView: NSTableView,  objectValueForTableColumn aTableColumn: NSTableColumn?,                        row rowIndex: Int) -> a href="" AnyObject /a ?  

Add this method implementation to the AppDelegate class:

    func tableView(tableView: NSTableView, objectValueForTableColumn tableColumn: NSTableColumn?, row: Int) -> AnyObject? {          if row < 0 || row >= sharedBrowser.sharedPointsList.count {              return ""          }          if let columnId = tableColumn?.identifier {              let sharedPoint = sharedBrowser.sharedPointsList[row]              if columnId == "name" {                  return sharedPoint.netService.name              } else if columnId == "domain" {                  return sharedPoint.netService.domain              } else if columnId == "type" {                  return sharedPoint.type              } else if columnId == "ip" {                  return sharedPoint.ipAddress              }          }          return ""      }  

In applicationDidFinishLaunching(_:) method set the AppDelegate class itself as the target for the sharedPointsTable, and set the selector for its doubleAction property. At double-click on the table’s row, mountToShare(sender:) method will be called, which is indented to create the connection to file sharing service, corresponding to the row. In this very method subscribe to NewSharedDetected notification, upon receipt of which reloadSharedPointsTable(), method will be called, which is indented to refresh sharedPointsTable table data.

    func applicationDidFinishLaunching(aNotification: NSNotification) {          // Insert code here to initialize your application          sharedPointsTable.target = self          sharedPointsTable.doubleAction = "mountToShare:"          NSNotificationCenter.defaultCenter().addObserver(self,selector: "reloadSharedPointsTable",                                                                                   name: "NewSharedDetected",                                                                                   object: sharedBrowser)     }  

Add reloadSharedPointsTable() method implementation:

    func reloadSharedPointsTable() {          sharedPointsTable.reloadData()      }  

mountToShare(sender:) function:

    func mountToShare(sender:NSTableView!) {          var clickedRow = sender.clickedRow          if (clickedRow < 0) ||             (clickedRow > (sharedBrowser.sharedPointsList.count - 1))  {              return          }            let sharedPoint = sharedBrowser.sharedPointsList.objectAtIndex[clickedRow]          let ip = sharedPoint.ipAddress          if let a = NSAppleScript(source:                                                  "tell application \"Finder\"\ntry\nmount volume \                                                  "\(sharedPoint.type)://\(ip)\"\nend try\nend tell\n") {              var errorInfo: NSDictionary?              if a.compileAndReturnError(&errorInfo) {                  if (a.executeAndReturnError(&errorInfo) != nil) {                      // success                  }              } else {              }              if errorInfo != nil {                  NSLog("Error %@",errorInfo!);              }          }      }  

Connection to file sharing service is processed via script on AppleScript. There’s NSAppleScript class to create and perform scripts in Cocoa applications.

NSAppleScript object creation is performed via init?(source: String) initializer, to which a string with script code is passed.

Application Launch

1. Launch the app by clicking Run button.

2. Click Search button.

3. In the table, you will see computers’ names with enabled file sharing, the protocol type used for sharing and their IP addresses.

tumblr_inline_ndfuojWLlG1sit1v2

4. To connect to a definite computer with enabled file sharing, double-click the row corresponding to it. You’ll see the window:

tumblr_inline_ndfuo8s3881sit1v2

Congrats! That’s your first Swift app!

Download the archive with sources of the above described sample here: SharingBrowser