Get the name of a connected screen Swift - swift

Does anyone know of a way to get the screen name, or model name/number from a display that is connected to the system? I've been looking around for quite some time to see if there is a way to do this. The only method I've seen anyone post only works with a deprecated API (CGDisplayIOServicePort), (and there's not replacement listed for that API), so that isn't really an option.
Basically, I am wanting to give the user a list of connected screens to display the output of the app, and I feel like giving them a list of names of the displays would be much more elegant and nicer than whatever the ID is that is returned from NSScreen or CGGetActiveDisplayList, etc. It has to possible, when you go to the display preferences in OS X it gives you the names of the displays there. Anyone have any ideas?

macOS 10.15 Catalina introduced a new property localizedName for getting the external display name:
NSScreen.screens.forEach {
print($0.localizedName)
}

You can get the names of connected screens directly from IOReg
func screenNames() -> [String] {
var names = [String]()
var object : io_object_t
var serialPortIterator = io_iterator_t()
let matching = IOServiceMatching("IODisplayConnect")
let kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault,
matching,
&serialPortIterator)
if KERN_SUCCESS == kernResult && serialPortIterator != 0 {
repeat {
object = IOIteratorNext(serialPortIterator)
let info = IODisplayCreateInfoDictionary(object, UInt32(kIODisplayOnlyPreferredName)).takeRetainedValue() as NSDictionary as! [String:AnyObject]
if let productName = info["DisplayProductName"] as? [String:String],
let firstKey = Array(productName.keys).first {
names.append(productName[firstKey]!)
}
} while object != 0
}
IOObjectRelease(serialPortIterator)
return names
}
let names = screenNames()

Related

Swift Firebase Firestore userlist (too many reads)

I'm trying to find a solution for a little problem I have with my app. It's a chess app and it works with Firebase and is written in swift. I use the database for user authentication, user information and uploading moves to play against each other online. All the userInfo is saved in a document in the collection "allUsers".
Everything is working fine, but I have a user screen where you can press a refresh button to update the current online users, with the .getDocuments() function. The problem is that every time a user refreshes, they query through all of the registered accounts, check if they have the flag "isOnline = true" and then list only those users in a TableView. I believe this counts in firebase as 1 read for every registered account, even though the user is not online.
The app is already live in the AppStore and I have quite a few people registered already. About 300. But, to be honest a lot of people just try it once or a few times and then leave again and never use the app again. But every time someone wants to update the online users, they cycle through 300 users and this gives me 300 reads with firebase. Right now it's not a big problem, but once people really start to use the app, I will reach my quotum quite quickly.
I could try .addSnapshotListener , but this will refresh the user screen everytime something happens in the userlist. It will be too chaotic. I've read about reading data from cache, but I'm not sure how to go about this. I could also get a fresh userlist when the app starts, save it locally and check every now and then if there are new online users, but I want the list to be updated whenever the user wants to.
Is there a way to compare a locally saved list to the online database list and only read/get the documents that are changed / new?
Sorry for the long text. Hopefully anyone can help me out!
Thanks.
Below is my code to load the users. It's a bit messy sorry.. Basically it retrieves all users and sorts them by online and offline. If a user searches for another user, it takes a String "query" as input.
Code :
func loadAllAvailableUsers(query : String) {
availableEmails = []
availableUsers = []
onlineUsers = []
isInGameIndex = []
var av : [String] = []
var ae : [String] = []
var wins : [Int] = []
var losses : [Int] = []
var draw : [Int] = []
var matches : [Int] = []
let collection = db.collection("allUsers")
collection.getDocuments { (snapshot, err) in
if let e = err {
print(e.localizedDescription)
} else {
if let documents = snapshot?.documents {
print("found users")
for doc in documents {
print(doc.documentID)
if doc.documentID != currentEmail && query == "" {
print(doc.data()["isOnline"] as! Bool)
if let name = doc.data()["username"] as? String, let w = doc.data()["wins"] as? Int, let l = doc.data()["losses"] as? Int, let d = doc.data()["draw"] as? Int, let numOfMatches = doc.data()["numberOfMatches"] as? Int, let online = doc.data()["isOnline"] as? Bool {
print("adding user : \(name) to list")
if online {
matches.append(numOfMatches)
av.append(name)
ae.append(doc.documentID)
wins.append(w)
losses.append(l)
draw.append(d)
onlineUsers.append(name)
if doc.data()["isInGame"] as! Bool == true {
print(i)
self.delegate?.addToInGameIndex(name: name)
}
}
}
} else if query != "" {
if let name = doc.data()["username"] as? String, let w = doc.data()["wins"] as? Int, let l = doc.data()["losses"] as? Int, let d = doc.data()["draw"] as? Int, let online = doc.data()["isOnline"] as? Bool, let numOfMatches = doc.data()["numberOfMatches"] as? Int {
print("Searched : adding user : \(name) to list")
if doc.documentID == currentEmail {
continue
}
let lowerName = name.lowercased()
let lowerQuery = query.lowercased()
if lowerName.contains(lowerQuery) && online {
av.append(name)
ae.append(doc.documentID)
wins.append(w)
losses.append(l)
draw.append(d)
matches.append(numOfMatches)
if doc.data()["isInGame"] as! Bool == true {
print(i)
self.delegate?.addToInGameIndex(name: name)
}
onlineUsers.append(name)
} else if lowerName.contains(lowerQuery) && !online {
av.append(name)
ae.append(doc.documentID)
wins.append(w)
losses.append(l)
draw.append(d)
matches.append(numOfMatches)
}
}
}
}
}
if ae.count > 0 {
self.delegate?.reloadTheTable(wins: wins, losses: losses, draw: draw, ae: ae, au: av, matches: matches)
}
print(availableUsers)
}
}
}

ITunes Library Framework in Swift 5.2

I have searched everywhere and I am unable to find a swifty example of how to use the Apple ITunesLibraryFramework. I have been trying to figure out how to use this Framework in Swift 5.2.
I want to get information directly from the Music library rather than having to rely on a XML library export file.
I have the below code in my playground and it prints nothing legible. How would I access the fields for playlists and mediaItems and be able to read them in human readable form?
I have installed the framework in the project. This is my project playground code:
import Foundation
import iTunesLibrary
var library:ITLibrary
do {
let library = try ITLibrary(apiVersion: "1.1")
let mediaItems = library.allMediaItems
let playlists = library.allPlaylists
print("Media Folder Location - \(String(describing: library.mediaFolderLocation))")
print("\nPlaylists - \(playlists)")
print("\nTracks - \(mediaItems)")
} catch let error as NSError {
print(error)
}
This is the ITLibrary.m file that I imported the header:
#import <Foundation/Foundation.h>
#import <iTunesLibrary/iTunesLibrary.h>
When I run the above code in the project playground all I get is a bunch of binary data for both playlists and mediaItems. All I want to do is iterate over the data and collect information from the library for use in my program. It's probably something easy, but I haven't found it yet.
EDIT: - After using #Vincent's answer I ran into another problem with the following code:
import Foundation
import iTunesLibrary
let library = try ITLibrary(apiVersion: "1.1")
typealias tracks = [NSNumber:TrackInfo]
var trackInfo = TrackInfo()
struct TrackInfo {
var title = ""
var artist = ""
var album = ""
var totalTime = 0
var year = 0
var persistentID = ""
var location:URL!
}
let songs = library.allMediaItems
let playlists = library.allPlaylists
for playlist in playlists {
if playlist.name.lowercased().contains("test-") {
print("New Playlist---------------\nName: \(playlist.name)")
for song in playlist.items {
trackInfo.title = song.title
print("\n\nNew Track-----------------\nTitle: \(trackInfo.title)")
if song.artist?.name != nil {
trackInfo.artist = song.artist?.name as! String
}
print("Artist: \(trackInfo.artist)")
trackInfo.album = song.album.title!
print("Albumn Name: \(trackInfo.album)")
trackInfo.totalTime = song.totalTime
print("Total Time: \(trackInfo.totalTime)")
trackInfo.year = song.year
print("Year: \(trackInfo.year)")
trackInfo.location = song.location!
print("Location: \(trackInfo.location!)")
var persistentID = song.persistentID
tracks.updateValue(song.persistentID, trackInfo)
}
}
}
The issue I'm having is getting the tracks info into the trackInfo dictionary. I'm trying to use the track persistentID (NSNumber) as the key for the dictionary, which I have declared. For some reason it isn't allowing me to use it.
Here's how you can have it print each playlist and track:
Each ITLibPlaylist or ITLibMediaItem object contains many information about each playlist/media item. To get only the name/title of each, you will have to iterate through the results to retrieve them.
For this example below, the name of each playlist's name is printed.
print("\nPlaylists -")
for playlist in playlists {
print(playlist.name)
}
Which will print (for example):
Playlists -
Library
Music
For this example below, the name of each track's name is printed.
print("\nTracks -")
for mediaItem in mediaItems {
print(mediaItem.title)
}
Which will print (for example):
Tracks -
Ev'ry Time We Say Goodbye
My Favorite Things
But Not for Me
Summertime
Edit: Here's the secondary solution to the secondary problem:
First things first, a dictionary should be initialised, instead of using typealias.
typealias only makes an alias for a pre existing type, like
typealias NumberWithAlotOfDecimals = Double
let a: NumberWithAlotOfDecimals = 10.1
let b: Double = 10.1
both will a and b are Double, as NumberWithAlotOfDecimals is just an alias for Double.
Here's how to initialise:
//typealias tracks = [NSNumber:TrackInfo] // not this
var tracks = [NSNumber:TrackInfo]() // but this
Secondly, nullable objects should be properly handled
if let artistName = song.artist?.name {
trackInfo.artist = artistName
}
and
if let title = song.album.title {
trackInfo.album = title
}
if let location = song.location {
trackInfo.location = location
print("Location: \(location)")
}
instead of
if song.artist?.name != nil {
trackInfo.artist = song.artist?.name as! String
}
Please do not use ! to force unwrap nullable objects as that will cause runtime crashes when the object is nil.
Lastly, this is the way to store key value into dictionary in Swift.
let persistentID = song.persistentID
//tracks.updateValue(song.persistentID, trackInfo) // not this
tracks[persistentID] = trackInfo // this

Why is my lineString not converting to mapShape in geoSwift - (only happens with one specific search), could be external library bug?

Im using the GEOSwift Library: https://github.com/GEOSwift/GEOSwift
My best guess is that if you look at the string image linked, it looks as if its not a proper circle, so maybe it is a bug in the library? But i am not at all sure about this!
Im having an issue only when i enter one specific linestring.
My app takes an array of route coordinates, converts them into WKT String (representing a line). It then Creates a buffer around this line, then converts this into a mapShape.
It runs fine, until i search one specific route.
It fails here:
func bufferPolyline(routeCoords: [CLLocationCoordinate2D], completion: #escaping (_ bufferCoordsArray: [LatLng]) -> ()) {
var wktString = ""
var i = 0
while i < routeCoords.count {
let lat = routeCoords[i].latitude
let lng = routeCoords[i].longitude
if i == routeCoords.count-1 {
let wktLast = " \(lng) \(lat)"
wktString += "\(wktLast)"
i += 1
}
if i >= 1 && i <= routeCoords.count-2 {
let wktMid = " \(lng) \(lat),"
wktString += "\(wktMid)"
i += 1
}
if i == 0 {
let wktFirst = "\(lng) \(lat),"
wktString += "\(wktFirst)"
i += 1
}
}
let linestring = Geometry.create("LINESTRING(\(wktString))")!
let string = linestring.buffer(width: 0.05)!
guard let shapeLine = string.mapShape() as? MKPolygon else {
preconditionFailure() // FAILURE HAPPENS HERE.
}
}
Here are links to images to see how it looks:
LineString - https://imgur.com/a/7OLPZkM
String - https://imgur.com/a/KJRfpRX
the linestring, and string values are still coming through even when shapeLine doesnt initialise so im struggling to see where im going wrong. They also seem to be formatted the same way.
I tried to google for a WKT String validator, but didnt find one, but i assume it should be ok, as i return multiple other searches with no issues. (i.e. the shapeLine returns a value)
My question is: does this look like a problem in my code, or a possible bug of the library? (i have little faith in my code!)

Can you send objects other than strings in URLQueryItems?

Ok, I am building an iMessage app and to transfer data back and forth I have to use URLQueryItems. I am working with an SKScene and need to transfer Ints, CGPoints, images, etc. Reading Apple's documentation and my own attempts it seems like you can only store strings in URLQueryItems.
As this us the only way to pass data back and forth, is there a (better) way to store other types of data? Currently I have been doing this:
func composeMessage(theScene: GameScene) {
let conversation = activeConversation
let session = conversation?.selectedMessage?.session ?? MSSession()
let layout = MSMessageTemplateLayout()
layout.caption = "Hello world!"
let message = MSMessage(session: session)
message.layout = layout
message.summaryText = "Sent Hello World message"
var components = URLComponents()
let queryItem = URLQueryItem(name: "score",value: theScene.score.description)
components.queryItems = [queryItem] //array of queryitems
message.url = components.url!
print("SENT:",message.url?.query)
conversation?.insert(message, completionHandler: nil)
}
Then on the flip side I have to convert this string back to an Int again. Doing this with CGPoints will be inefficient.. how would one pass something like a CGPoint in a URLQueryItem? Any other way than storing the x and y values as strings?
EDIT: This is how I have been receiving data from the other person and putting into their scene:
override func willBecomeActive(with conversation: MSConversation) {
// Called when the extension is about to move from the inactive to active state.
// This will happen when the extension is about to present UI.
// Use this method to configure the extension and restore previously stored state.
let val = conversation.selectedMessage?.url?.query?.description
print("GOT IT ", val)
if(val != nil)
{
scene.testTxt = val!
}
}
As you discovered, to pass data via URLQueryItem, you do have to convert everything to Strings since the information is supposed to be represented as a URL after all :) For CGPoint information, you can break the x and y values apart and send them as two separate Ints converted to String. Or, you can send it as a single String value in the form of "10,5" where 10 is the x and 5 is the y value but at the other end you would need to split the value on a comma first and then convert the resulting values back to Ints, something like this (at the other end):
let arr = cgPointValue.components(separatedBy:",")
let x = Int(arr[0])
let y = Int(arr[1])
For other types of data, you'd have to follow a similar tactic where you convert the values to String in some fashion. For images, if you have the image in your resources, you should be able to get away with passing just the name or an identifying number. For external images, a URL (or part of one if the images all come from the same server) should work. Otherwise, you might have to look at base64 encoding the image data or something if you use URLQueryItem but if you come to that point, you might want to look at what you are trying to achieve and if perhaps there is a better way to do it since large images could result in a lot of data being sent and I'm not sure if iMessage apps even support that. So you might want to look into limitations in the iMessage app data passing as well.
Hope this helps :)
You can use iMessageDataKit library for storing key-value pairs in your MSMessage objects. It makes setting and getting data really easy and straightforward like:
let message: MSMessage = MSMessage()
message.md.set(value: 7, forKey: "moveCount")
message.md.set(value: "john", forKey: "username")
message.md.set(values: [15.2, 70.1], forKey: "startPoint")
message.md.set(values: [20, 20], forKey: "boxSize")
if let moveCount = message.md.integer(forKey: "moveCount") {
print(moveCount)
}
if let username = message.md.string(forKey: "username") {
print(username)
}
if let startPoint = message.md.values(forKey: "startPoint") {
print("x: \(startPoint[0])")
print("y: \(startPoint[1])")
}
if let boxSize = message.md.values(forKey: "boxSize") {
let size = CGSize(width: CGFloat(boxSize[0] as? Float ?? 0),
height: CGFloat(boxSize[1] as? Float ?? 0))
print("box size: \(size)")
}
(Disclaimer: I'm the author of iMessageDataKit)

Get Product Name Of USB Device From portIterator

Is there a way, using IOKit or something similar that does not involve downloading additional packages from the internet, that I can use to read a USB device's product name?
This is my current code...
func printSerialPaths(portIterator: io_iterator_t) {
var serialService: io_object_t
repeat {
serialService = IOIteratorNext(portIterator)
if (serialService != 0) {
var key: CFString! = "IOCalloutDevice"
var bsdPathAsCFtring: AnyObject? = IORegistryEntryCreateCFProperty(serialService, key, kCFAllocatorDefault, 0).takeUnretainedValue()
var bsdPath = bsdPathAsCFtring as! String?
if let path = bsdPath {
print(path)
}
var deviceNameCString: [CChar] = [CChar](count: 128, repeatedValue: 0)
let deviceNameResult = IORegistryEntryGetName(serialService, &deviceNameCString)
let deviceName = String.fromCString(&deviceNameCString)!
print("usb Device Name: \(deviceName)")
}
} while serialService != 0;
}
I have also tried using other CFStrings, such as "Product Name" in the IORegistryEntryCreateCFProperty() command as I've seen suggested elsewhere with no luck. If replacing that is all I need, where can I find the documentation for the rest of these strings?
The product name that I'm talking about is highlighted below. I'm not sure what its technical name would be.
If the io_service_t handle is to an IOUSBDevice/IOUSBHostDevice, it should have a property named "USB Product Name" (symbolic constant kUSBProductString, at least in C) - I believe that's what you're after. You can query it with IORegistryEntryCreateCFProperty() as you're already doing for the "IOCalloutDevice" property, which by the way is defined as the symbolic constant kIOCalloutDeviceKey.
If those constants do not exist in Swift when importing the IOKit module, just define your own constants and file a bug (Radar) with Apple about the omission.