I am trying to implement my own web browser history for WKWebView on iOS, but I can't implement this functionality completely, and each time I obtain trouble.
I can create a history where the user did be and then moving forward and backward inside history.
But I have next trouble, and I think it an only one of many problems on my way.
When I have a history with for example 10 elements, and then I am moving back to element number 5 and then go don't forward but try to open the new link I can't remove element 6-10 and put the new link.
I think my problem that I can't fully understand how history work inside all browsers under the hood, this is not a hard task but I am confused inside this algorithm.
My main data structure for holding history
Help me understand how to work this algorithm inside browsers or maybe exist a good theory about it?
I have solved this problem and realize the full algorithm well, the completed project available here: https://github.com/IhorYachmenov/Custom-browser-history-for-WKWebView.
Algorithm:
struct PlayerHistory {
static var shared = PlayerHistory()
var historyExist: Bool = false
var historyCurrentPosition: Int = 0
var historyLastPositionBeforeUpdatingHistory: Int!
var userHistoryKey: String!
var backPressed: Bool!
var forwardPressed: Bool!
var urlOfPlayer: String!
// Function only for first loading inside <viewDidLoad or another method from app LifeCycle>.
mutating func getUrlForFirstLoading(initURL: String, key: String) -> String {
urlOfPlayer = initURL
guard HistoryStorage.shared.getHistoryFromUserDefaults() != nil else {
updateFirstElement(key: key, url: initURL)
return initURL
}
guard HistoryStorage.shared.getHistoryFromUserDefaults()![key] != nil else {
return initURL
}
let position = HistoryStorage.shared.getHistoryFromUserDefaults()![key]!.count - 1
historyExist = true
historyCurrentPosition = position
userHistoryKey = key
let initUrlFromHistoryStorage = HistoryStorage.shared.getHistoryFromUserDefaults()![key]!.last!.url
return initUrlFromHistoryStorage
}
// Create new or update exist history, use this method indsede <decidePolicyForNavigation>.
mutating func updatePlayerHistory(backlisk: [String], key: String) {
var history = [WebViewHistory]()
for i in backlisk {
history.append(WebViewHistory(i))
}
if (historyExist == true) {
// If old history exist need compound both and then to save.
let oldHistory = HistoryStorage.shared.getHistoryFromUserDefaults()![key]
let oldAndNewHostoryTogether = oldHistory! + history
var keyValuePair = Dictionary<String, [WebViewHistory]>()
keyValuePair.updateValue(oldAndNewHostoryTogether, forKey: key)
HistoryStorage.shared.removeHistory()
HistoryStorage.shared.saveHistory(keyValuePair)
setCurrentPosition(url: backlisk.last!, key: key)
} else {
var keyValuePair = Dictionary<String, [WebViewHistory]>()
keyValuePair.updateValue(history, forKey: key)
historyExist = true
HistoryStorage.shared.removeHistory()
HistoryStorage.shared.saveHistory(keyValuePair)
setCurrentPosition(url: backlisk.last!, key: key)
}
}
// Before using this method check if result don't equals nil. Use this method for navigation beetween history
func moveThroughHistory(key: String, direction: Bool) -> String? {
guard historyExist != false else {
return nil
}
let history = HistoryStorage.shared.getHistoryFromUserDefaults()![key]!
if (direction == true) {
let index = historyCurrentPosition + 1
guard index != history.count else { return nil }
return history[index].url
} else {
let index = historyCurrentPosition - 1
guard index > 0 else { return history[0].url }
return history[index].url
}
}
// Method <setCurrentPosition> each time set position at history
mutating func setCurrentPosition(url: String, key: String) {
guard HistoryStorage.shared.getHistoryFromUserDefaults() != nil else { return }
guard HistoryStorage.shared.getHistoryFromUserDefaults()![key] != nil else { return }
let history = HistoryStorage.shared.getHistoryFromUserDefaults()![key]
let index = history?.firstIndex(of: WebViewHistory(url))
guard index != nil else {
historyCurrentPosition = 0
return
}
historyCurrentPosition = index!
}
// <removeUnusedPeaceOfHistory> need use when user want open new page staying inside the middle of history
mutating func removeUnusedPeaceOfHistory(key: String) {
guard HistoryStorage.shared.getHistoryFromUserDefaults() != nil else {
return
}
guard HistoryStorage.shared.getHistoryFromUserDefaults()![key] != nil else {
return
}
var history = HistoryStorage.shared.getHistoryFromUserDefaults()![key]!
let startIndex = historyCurrentPosition + 1
let endIndex = history.endIndex - 1
let countOfAllElements = history.count
guard startIndex != countOfAllElements else { return }
let range = startIndex...endIndex
history.removeSubrange(range)
var keyValuePair = Dictionary<String, [WebViewHistory]>()
keyValuePair.updateValue(history, forKey: key)
HistoryStorage.shared.removeHistory()
HistoryStorage.shared.saveHistory(keyValuePair)
}
// Use <updateFirstElement> inside <getUrlForFirstLoading> if history doesn't exist
private mutating func updateFirstElement(key: String, url: String) {
var history = [WebViewHistory]()
history.insert(WebViewHistory(url), at: 0)
var keyValuePair = Dictionary<String, [WebViewHistory]>()
keyValuePair.updateValue(history, forKey: key)
HistoryStorage.shared.saveHistory(keyValuePair)
historyExist = true
historyCurrentPosition = 0
}
// Use <webViewWillBeClosedSaveHistory> when WKWebView should be closed, if the user moves through history new position will be saved.
mutating func webViewWillBeClosedSaveHistory(key: String) {
let history = HistoryStorage.shared.getHistoryFromUserDefaults()![key]!
let currentPosition = historyCurrentPosition + 1
guard currentPosition != history.count else { return }
removeUnusedPeaceOfHistory(key: key)
}
}
I am making my own Spotify controller to learn how to interact with AppleScript and Swift (using Objective-C as a bridge) to make a bigger project later on. The only issue is the current progress time seems to update slightly faster then the time left.
This is what my application looks like:
The time on the left seems to update slightly faster then the time on the right, this means it is not accurate and can be bit strange to look at. How would I be able to fix this so both the progress time and time left update at the same time?
Here is my code:
ViewController.swift
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var statusLabel: NSTextField!
#IBOutlet weak var songProgress: NSProgressIndicator!
#IBOutlet weak var lblCurrentProgress: NSTextField!
#IBOutlet weak var lblTrackLength: NSTextField!
//let debugger = Debug(appName:"Spotify Menu", debugable: )
var previousScript:String = ""
var toggleScript:String = ""
var nextScript:String = ""
var statusScript:String = ""
var artistScript:String = ""
var songNameScript:String = ""
var trackPosScript:String = ""
var trackLengthScript:String = ""
var trackIDscript:String = ""
var trackName:String = "unknown"
var artistName:String = "unknown"
var playerStatus:String = "unknown"
var trackID:String = "unknown"
var playerPosistion:Double = -1.0
var trackLength:Double = -1.0
var percentThrough:Double = -1.0
let timeFormatter = twoDiffrentTimes()
func main()
{
getStatus()
getSongName()
getArtist()
getTrackLength()
getPlayerPos()
getID()
percentThrough = (playerPosistion/trackLength)*100
if(playerStatus == "playing" && trackName != "unknown" && artistName != "unknown")
{
statusLabel.stringValue = "Spotify is currently playing the song \"\(trackName)\" by \"\(artistName)\""
} else if(playerStatus == "paused"){
statusLabel.stringValue = "Spotify is currently paused"
} else if(playerStatus == "stopped") {
statusLabel.stringValue = "Spotify is not playing any music"
} else {
statusLabel.stringValue = "There is currently an error"
}
//NSLog("The song is currently \(percentThrough.toDecimalPlace("2"))% the way in")
songProgress.doubleValue = percentThrough
timeFormatter.update(playerPosistion, timeTwo: trackLength-playerPosistion)
lblCurrentProgress.stringValue = timeFormatter.timeOneString
lblTrackLength.stringValue = timeFormatter.timeTwoString
}
override func viewDidLoad() {
super.viewDidLoad()
previousScript = readFile("previousTrack")
if(previousScript == "")
{
NSLog("Error finding previous track script")
exit(1)
}
toggleScript = readFile("playPause")
if(toggleScript == "")
{
NSLog("Error finding toggle script")
exit(1)
}
nextScript = readFile("nextTrack")
if(nextScript == "")
{
NSLog("Error going forwared a track")
exit(1)
}
statusScript = readFile("playerStatus")
if(statusScript == "")
{
NSLog("Error going back a track")
statusLabel.stringValue = "Spotify is in an unknown status"
exit(1)
}
artistScript = readFile("getArtist")
if(artistScript == "")
{
NSLog("Cannot get artist script")
exit(1)
}
songNameScript = readFile("getSongName")
if(songNameScript == "")
{
NSLog("Cannot get song name script")
exit(1)
}
trackPosScript = readFile("playerPosition")
if(trackPosScript == "")
{
NSLog("Error getting track posistion script")
exit(1)
}
trackLengthScript = readFile("durationOfSong")
if(trackLengthScript == "")
{
NSLog("Error getting track length script")
exit(1)
}
trackIDscript = readFile("trackID")
if(trackIDscript == "")
{
NSLog("Error getting")
}
main()
NSTimer.scheduledTimerWithTimeInterval(0.3, target: self, selector: Selector("main"), userInfo: nil, repeats: true)
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func previous(sender: AnyObject)
{
runAppleScript(previousScript)
}
#IBAction func toggle(sender: AnyObject)
{
runAppleScript(toggleScript)
}
#IBAction func next(sender: AnyObject)
{
let nextFile = readFile("nextTrack")
if(nextFile == "")
{
NSLog("Error going forwared a track")
} else {
runAppleScript(nextFile)
}
}
func getPlayerPos()
{
playerPosistion = runAppleScript(trackPosScript).toDouble()
}
func getTrackLength()
{
trackLength = runAppleScript(trackLengthScript).toDouble()
}
func getSongName()
{
trackName = runAppleScript(songNameScript)
}
func getArtist()
{
artistName = runAppleScript(artistScript)
}
func getID()
{
trackID = runAppleScript(trackIDscript)
}
func getStatus()
{
let resultData = runAppleScript(statusScript)
if(resultData == "playing" || resultData == "paused" || resultData == "stopped")
{
playerStatus = resultData
} else {
playerStatus = "unknown"
}
}
}
classes.swift
import Foundation
class twoDiffrentTimes
{
var timeOne:Double = 0.0
var iMinuteOne:Int = 0
var iSecondsOne:Int = 0
var minuteOne:String = ""
var secondsOne:String = ""
var timeOneString:String = ""
var uSecondsOne:Double = 0.0
var temp:Int = 0
var timeTwo:Double = 0.0
var iMinuteTwo:Int = 0
var iSecondsTwo:Int = 0
var minuteTwo:String = ""
var secondsTwo:String = ""
var timeTwoString:String = ""
var uSecondsTwo:Double = 0.0
func update(timeOne:Double, timeTwo:Double)
{
//First Time
self.temp = Int(timeOne)
self.iMinuteOne = temp / 60
self.iSecondsOne = temp % 60
self.minuteOne = "\(iMinuteOne)"
if(self.iSecondsOne < 10)
{
self.secondsOne = "0\(self.iSecondsOne)"
} else {
self.secondsOne = "\(self.iSecondsOne)"
}
self.timeOneString = "\(self.iMinuteOne).\(self.secondsOne)"
//Second Time
self.temp = Int(timeTwo)
self.iMinuteTwo = temp / 60
self.iSecondsTwo = temp % 60
self.minuteTwo = "\(iMinuteTwo)"
if(self.iSecondsTwo < 10)
{
self.secondsTwo = "0\(self.iSecondsTwo)"
} else {
self.secondsTwo = "\(self.iSecondsTwo)"
}
self.timeTwoString = "\(self.iMinuteTwo).\(self.secondsTwo)"
}
}
extensions.swift
import Foundation
extension String {
func toDouble() -> Double {
if let unwrappedNum = Double(self) {
return unwrappedNum
} else {
// Handle a bad number
print("Error converting \"" + self + "\" to Double")
return 0.0
}
}
}
extension Double {
func toDecimalPlace(f: String) -> String {
return NSString(format: "%0.\(f)f", self) as String
}
func decimalValue() -> Int
{
if let decimalString = String(self).componentsSeparatedByString(".").last, decimal = Int(decimalString) {
return decimal // 535
} else {
return -1
}
}
}
functions.swift
import Foundation
func readFile(fileName: String) -> String{
if let filepath = NSBundle.mainBundle().pathForResource(fileName, ofType: "txt") {
do {
let contents = try NSString(contentsOfFile: filepath, usedEncoding: nil) as String
print(contents)
return contents
} catch {
NSLog("Content Could Not Be Loaded")
return ""
}
} else {
NSLog("File \(fileName).\("txt") could not be found")
return ""
}
}
func runAppleScript(script:String) -> String
{
let theResult:String
let errorInfo = AutoreleasingUnsafeMutablePointer<NSDictionary?>()
let startAtLoginScript: NSAppleScript = NSAppleScript(source: script)!
let theDiscriptor:NSAppleEventDescriptor = startAtLoginScript.executeAndReturnError(errorInfo)
if let _ = theDiscriptor.stringValue
{
theResult = theDiscriptor.stringValue!
} else {
theResult = ""
}
return theResult
}
errorClases.swift
import Foundation
enum MyAppleScriptError: ErrorType {
case ExecutingScriptFailed
case GettingStringValueFailed
}
Last but not least durationOfSong.txt (AppleScript)
tell application "Spotify"
set songLength to (duration of current track) / 1000 as string
return songLength
end tell
And playerPosition
tell application "Spotify"
set currentPosistion to player position as string
return currentPosistion
end tell
Edit:
Updated code
I recommend you learn how to call AppleScript handlers directly from Swift via the AppleScript-ObjC bridge. That will work vastly better than mucking about with code strings and NSAppleScript, which is slow and nasty and not a good way to do it. Tutorial here:
http://macscripter.net/viewtopic.php?id=43127
--
ETA:
"I have successful moved NSAppleScript to Swift2. Only thing is that still doesn't fix the issue. it is still out of timing."
Well, that's annoying. FWIW, I'd ditch that twoDiffrntTimes class as it doesn't do anything that can't be done in a single-line function, and change the end of main() to this:
func main() {
...
lblCurrentProgress.stringValue = formatTime(Int(playerPosistion))
lblTrackLength.stringValue = formatTime(Int(trackLength-playerPosistion))
}
func formatTime(secs: Int) -> String {
return NSString(format: "%i.%02i", secs / 60, secs % 60) as String
}
//
// ViewController.swift
// Arithmetic Calculator
//
// Created by Mapondera, Tanaka on 10/7/15.
// Copyright (c) 2015 Mapondera, Tanaka. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var calculatorDisplay: UILabel!
var isEnteredNumber = false
var firstNumber:Double = 0.0
var secondNumber:Double = 0.0
var operation = ""
var result:Double = 0.0
#IBAction func numberPunched(sender: AnyObject) {
// number
var number = sender.currentTitle
if isEnteredNumber {
calculatorDisplay.text = calculatorDisplay.text! + number!!
} else {
calculatorDisplay.text = number
isEnteredNumber = true
}
}
#IBAction func calculationPunched(sender: AnyObject) {
// operation
isEnteredNumber = false
firstNumber = calculatorDisplay.text?
// There is an error code for this line above that reads "cannot assign a value of type string to value of type double"...?
operation = sender.currentTitle!!
}
#IBAction func equalsPunched(sender: AnyObject) {
// equals
isEnteredNumber = false
var result = 0.0
secondNumber = calculatorDisplay.text?
// Error code for line above that reads "? must be followed by call, member lookup or subcrpit"...?
if operation == "+" {
result = firstNumber + secondNumber
} else if operation == "-" {
result = firstNumber - secondNumber
} else if operation == "x" {
result = firstNumber * secondNumber
} else if operation == "/" {
result = firstNumber / secondNumber
} /*else if operation == "^" {
result = pow(firstNumber, secondNumber)
}*/
calculatorDisplay.text = "\(result)"
}
#IBAction func changeSignPunched(sender: AnyObject) {
isEnteredNumber = false
firstNumber = calculatorDisplay.text?
// There is an Error code for the line above that reads "? must be followed by call, member lookup or subscrpit"...?
result = firstNumber * (-1)
calculatorDisplay.text = "\(result)"
}
#IBAction func clearPunched(sender: AnyObject) {
firstNumber = 0
secondNumber = 0
isEnteredNumber = false
result = 0
calculatorDisplay.text = "\(result)"
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You're trying to assign a String value to a variable you've already declared to be a Double. Try something like this for the lines that are giving you errors:
let firstNumberString = calculatorDisplay.text ?? ""
firstNumber = Double(firstNumberString) ?? 0.0
The ?? above is called the nil coalescing operator. It will unwrap an optional on the left side or return a default value on the right.
HTTPSecurity.swift:124:22: Cannot invoke 'SecPolicyCreateSSL' with an argument list of type '(Bool, String?)'
I'm getting the above error when trying to build a project containing this code:
public func isValid(trust: SecTrustRef, domain: String?) -> Bool {
var tries = 0
while(!self.isReady) {
usleep(1000)
tries += 1
if tries > 5 {
return false //doesn't appear it is going to ever be ready...
}
}
var policy: SecPolicyRef
if self.validatedDN {
policy = SecPolicyCreateSSL(true, domain)
} else {
policy = SecPolicyCreateBasicX509()
}
SecTrustSetPolicies(trust,policy)
if self.usePublicKeys {
if let keys = self.pubKeys {
var trustedCount = 0
let serverPubKeys = publicKeyChainForTrust(trust)
for serverKey in serverPubKeys as [AnyObject] {
for key in keys as [AnyObject] {
if serverKey.isEqual(key) {
trustedCount++
break
}
}
}
if trustedCount == serverPubKeys.count {
return true
}
}
} else if let certs = self.certificates {
let serverCerts = certificateChainForTrust(trust)
var collect = Array<SecCertificate>()
for cert in certs {
if let c = SecCertificateCreateWithData(nil,cert) {
collect.append(c)
}
}
SecTrustSetAnchorCertificates(trust,collect)
var result: SecTrustResultType = 0
SecTrustEvaluate(trust,&result)
let r = Int(result)
if r == kSecTrustResultUnspecified || r == kSecTrustResultProceed {
var trustedCount = 0
for serverCert in serverCerts {
for cert in certs {
if cert == serverCert {
trustedCount++
break
}
}
}
if trustedCount == serverCerts.count {
return true
}
}
}
return false
}
This code is from:
https://github.com/mpclarkson/SwiftHTTP/blob/swift-2/HTTPSecurity.swift#L124
Method using old fashion Boolean which is in fact UInt8. Method also use CFString and not String.
Easy solution is following:
policy = SecPolicyCreateSSL(1, domain as CFString)
More sophisticated solution is use extension for Bool:
extension Bool {
var booleanValue : Boolean {
return self ? 1 : 0
}
init(booleanValue : Boolean) {
self = booleanValue == 0 ? false : true
}
}
Then you can use:
policy = SecPolicyCreateSSL(true.booleanValue, domain as CFString)
Is it possible to get the MAC address using Swift?
The MAC address being the primary address for the Wi-Fi or Airport.
I'm trying to make a OS X application.
Apple's sample code from https://developer.apple.com/library/mac/samplecode/GetPrimaryMACAddress/Introduction/Intro.html to retrieve the Ethernet MAC address can
be translated to Swift. I have preserved only the most important
comments, more explanations can be found in the original code.
// Returns an iterator containing the primary (built-in) Ethernet interface. The caller is responsible for
// releasing the iterator after the caller is done with it.
func FindEthernetInterfaces() -> io_iterator_t? {
let matchingDictUM = IOServiceMatching("IOEthernetInterface");
// Note that another option here would be:
// matchingDict = IOBSDMatching("en0");
// but en0: isn't necessarily the primary interface, especially on systems with multiple Ethernet ports.
if matchingDictUM == nil {
return nil
}
let matchingDict = matchingDictUM.takeUnretainedValue() as NSMutableDictionary
matchingDict["IOPropertyMatch"] = [ "IOPrimaryInterface" : true]
var matchingServices : io_iterator_t = 0
if IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &matchingServices) != KERN_SUCCESS {
return nil
}
return matchingServices
}
// Given an iterator across a set of Ethernet interfaces, return the MAC address of the last one.
// If no interfaces are found the MAC address is set to an empty string.
// In this sample the iterator should contain just the primary interface.
func GetMACAddress(intfIterator : io_iterator_t) -> [UInt8]? {
var macAddress : [UInt8]?
var intfService = IOIteratorNext(intfIterator)
while intfService != 0 {
var controllerService : io_object_t = 0
if IORegistryEntryGetParentEntry(intfService, "IOService", &controllerService) == KERN_SUCCESS {
let dataUM = IORegistryEntryCreateCFProperty(controllerService, "IOMACAddress", kCFAllocatorDefault, 0)
if dataUM != nil {
let data = dataUM.takeRetainedValue() as! NSData
macAddress = [0, 0, 0, 0, 0, 0]
data.getBytes(&macAddress!, length: macAddress!.count)
}
IOObjectRelease(controllerService)
}
IOObjectRelease(intfService)
intfService = IOIteratorNext(intfIterator)
}
return macAddress
}
if let intfIterator = FindEthernetInterfaces() {
if let macAddress = GetMACAddress(intfIterator) {
let macAddressAsString = ":".join(macAddress.map( { String(format:"%02x", $0) } ))
println(macAddressAsString)
}
IOObjectRelease(intfIterator)
}
The only "tricky" part is how to work with Unmanaged objects, those
have the suffix UM in my code.
Instead of returning an error code, the functions return an optional
value which is nil if the function failed.
Update for Swift 3:
func FindEthernetInterfaces() -> io_iterator_t? {
let matchingDict = IOServiceMatching("IOEthernetInterface") as NSMutableDictionary
matchingDict["IOPropertyMatch"] = [ "IOPrimaryInterface" : true]
var matchingServices : io_iterator_t = 0
if IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &matchingServices) != KERN_SUCCESS {
return nil
}
return matchingServices
}
func GetMACAddress(_ intfIterator : io_iterator_t) -> [UInt8]? {
var macAddress : [UInt8]?
var intfService = IOIteratorNext(intfIterator)
while intfService != 0 {
var controllerService : io_object_t = 0
if IORegistryEntryGetParentEntry(intfService, "IOService", &controllerService) == KERN_SUCCESS {
let dataUM = IORegistryEntryCreateCFProperty(controllerService, "IOMACAddress" as CFString, kCFAllocatorDefault, 0)
if let data = dataUM?.takeRetainedValue() as? NSData {
macAddress = [0, 0, 0, 0, 0, 0]
data.getBytes(&macAddress!, length: macAddress!.count)
}
IOObjectRelease(controllerService)
}
IOObjectRelease(intfService)
intfService = IOIteratorNext(intfIterator)
}
return macAddress
}
if let intfIterator = FindEthernetInterfaces() {
if let macAddress = GetMACAddress(intfIterator) {
let macAddressAsString = macAddress.map( { String(format:"%02x", $0) } )
.joined(separator: ":")
print(macAddressAsString)
}
IOObjectRelease(intfIterator)
}
Update for Swift 4.2
func FindEthernetInterfaces() -> io_iterator_t? {
let matchingDictUM = IOServiceMatching("IOEthernetInterface");
// Note that another option here would be:
// matchingDict = IOBSDMatching("en0");
// but en0: isn't necessarily the primary interface, especially on systems with multiple Ethernet ports.
if matchingDictUM == nil {
return nil
}
let matchingDict = matchingDictUM! as NSMutableDictionary
matchingDict["IOPropertyMatch"] = [ "IOPrimaryInterface" : true]
var matchingServices : io_iterator_t = 0
if IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &matchingServices) != KERN_SUCCESS {
return nil
}
return matchingServices
}
// Given an iterator across a set of Ethernet interfaces, return the MAC address of the last one.
// If no interfaces are found the MAC address is set to an empty string.
// In this sample the iterator should contain just the primary interface.
func GetMACAddress(_ intfIterator : io_iterator_t) -> [UInt8]? {
var macAddress : [UInt8]?
var intfService = IOIteratorNext(intfIterator)
while intfService != 0 {
var controllerService : io_object_t = 0
if IORegistryEntryGetParentEntry(intfService, kIOServicePlane, &controllerService) == KERN_SUCCESS {
let dataUM = IORegistryEntryCreateCFProperty(controllerService, "IOMACAddress" as CFString, kCFAllocatorDefault, 0)
if dataUM != nil {
let data = (dataUM!.takeRetainedValue() as! CFData) as Data
macAddress = [0, 0, 0, 0, 0, 0]
data.copyBytes(to: &macAddress!, count: macAddress!.count)
}
IOObjectRelease(controllerService)
}
IOObjectRelease(intfService)
intfService = IOIteratorNext(intfIterator)
}
return macAddress
}
func getMacAddress() -> String? {
var macAddressAsString : String?
if let intfIterator = FindEthernetInterfaces() {
if let macAddress = GetMACAddress(intfIterator) {
macAddressAsString = macAddress.map( { String(format:"%02x", $0) } ).joined(separator: ":")
print(macAddressAsString!)
}
IOObjectRelease(intfIterator)
}
return macAddressAsString
}
Different approach via if_msghdr
func MACAddressForBSD(bsd : String) -> String?
{
let MAC_ADDRESS_LENGTH = 6
let separator = ":"
var length : size_t = 0
var buffer : [CChar]
let bsdIndex = Int32(if_nametoindex(bsd))
if bsdIndex == 0 {
print("Error: could not find index for bsd name \(bsd)")
return nil
}
let bsdData = Data(bsd.utf8)
var managementInfoBase = [CTL_NET, AF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, bsdIndex]
if sysctl(&managementInfoBase, 6, nil, &length, nil, 0) < 0 {
print("Error: could not determine length of info data structure");
return nil;
}
buffer = [CChar](unsafeUninitializedCapacity: length, initializingWith: {buffer, initializedCount in
for x in 0..<length { buffer[x] = 0 }
initializedCount = length
})
if sysctl(&managementInfoBase, 6, &buffer, &length, nil, 0) < 0 {
print("Error: could not read info data structure");
return nil;
}
let infoData = Data(bytes: buffer, count: length)
let indexAfterMsghdr = MemoryLayout<if_msghdr>.stride + 1
let rangeOfToken = infoData[indexAfterMsghdr...].range(of: bsdData)!
let lower = rangeOfToken.upperBound
let upper = lower + MAC_ADDRESS_LENGTH
let macAddressData = infoData[lower..<upper]
let addressBytes = macAddressData.map{ String(format:"%02x", $0) }
return addressBytes.joined(separator: separator)
}
MACAddressForBSD(bsd: "en0")
Update to Martin R's entry. There are a couple of lines that won't compile with Swift 2.1.
Change:
let matchingDict = matchingDictUM.takeUnretainedValue() as NSMutableDictionary
To:
let matchingDict = matchingDictUM as NSMutableDictionary
Change:
let macAddressAsString = ":".join(macAddress.map( { String(format:"%02x", $0) } ))
To:
let macAddressAsString = macAddress.map( { String(format:"%02x", $0) } ).joinWithSeparator(":")
You can also use 'SystemConfiguration' framework
import SystemConfiguration
func collectMACAddresses() -> [String] {
guard let interfaces = SCNetworkInterfaceCopyAll() as? [SCNetworkInterface] else {
return []
}
return interfaces
.map(SCNetworkInterfaceGetHardwareAddressString)
.compactMap { $0 as String? }
}
DISCLAIMER: this is not production-ready. It would probably be rejected by the App Store. It's also subject to errors if the output of ifconfig changes in the future. I've made this because I lacked the skills to translate the C code given in the links. It does not replace a full Swift solution. That being said, it works...
Get ifconfig's output and parse it to get the MAC address associated with an interface (en0 in this example):
let theTask = NSTask()
let taskOutput = NSPipe()
theTask.launchPath = "/sbin/ifconfig"
theTask.standardOutput = taskOutput
theTask.standardError = taskOutput
theTask.arguments = ["en0"]
theTask.launch()
theTask.waitUntilExit()
let taskData = taskOutput.fileHandleForReading.readDataToEndOfFile()
if let stringResult = NSString(data: taskData, encoding: NSUTF8StringEncoding) {
if stringResult != "ifconfig: interface en0 does not exist" {
let f = stringResult.rangeOfString("ether")
if f.location != NSNotFound {
let sub = stringResult.substringFromIndex(f.location + f.length)
let range = Range(start: advance(sub.startIndex, 1), end: advance(sub.startIndex, 18))
let result = sub.substringWithRange(range)
println(result)
}
}
}