Creating a CTTypesetter in Swift - cocoa

I have an obj-c class that creates a CFAttributedStringRef:
- (CFAttributedStringRef)createAttributedStringWithByteRange:(NSRange)byteRange
{
UInt8 bytes[byteRange.length];
[self.data getBytes:&bytes range:byteRange];
CFStringRef string = CFStringCreateWithBytes(NULL, bytes, byteRange.length, kCFStringEncodingUTF8, (byteRange.location == 0));
return CFAttributedStringCreate(NULL, string, (__bridge CFDictionaryRef)(self.textAttributes));
}
And I'm trying to call that function from a swift object, which should create a CTTypesetter:
let stringToDraw = self.storage.createAttributedStringWithByteRange(self.line.range)
let typesetter = CTTypesetterCreateWithAttributedString(stringToDraw)
Which gives me this build error:
error: cannot convert the expression's type 'CTTypesetter!' to type '$T3'
let typesetter = CTTypesetterCreateWithAttributedString(stringToDraw)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Anybody have any idea what I'm doing wrong?

I found the problem, the build error was misleading.
The actual problem was the previous line — my obj-c method returning CFAttributedStringRef bridges to Unmanaged<CFAttributedString> and needs to be brought into Swift as a managed object before it can be used, via the takeRetainedValue() method:
let stringToDraw = self.storage.createAttributedStringWithByteRange(self.line.range).takeRetainedValue()
let typesetter = CTTypesetterCreateWithAttributedString(stringToDraw)

You need to refer to the object type as a CTTypesetterRef, not CTTypesetter.
import Foundation
import CoreText
extension NSString
{
func test() -> CTTypesetterRef
{
let attributedString : CFAttributedStringRef?
let typesetter : CTTypeSetterRef = CTTypesetterCreateWithAttributedString(attributedString)
return typesetter
}
}

Related

Could not cast value of type SINMessage to ChatMessage (Swift)

Currently trying to bridge the Sinch SDK into my Swift app and I'm running across the following error:
"Could not cast value of type 'SINMessageRebRTCImpl'" to 'ChatMessage'"
1) The type SINMessageRebTyCImpl is created in the following function located in the AppDelegate.swift file:
func messageSent(message: SINMessage, recipientId: String) {
self.saveMessagesOnParse(message)
**NSNotificationCenter.defaultCenter().postNotificationName(SINCH_MESSAGE_SENT, object: self, userInfo: ["message": message])** }
2) SINMessageRebTyCImpl is then passed to the following function located in one of my viewcontrollers
func messageDelievered(notification: NSNotification){
let chatMessage: ChatMessage = (notification.userInfo["message"] as! ChatMessage)
self.messageArray.append(chatMessage)
}
3) The code then crashes at the second line of the above snippet.
let chatMessage: ChatMessage = (notification.userInfo["message"] as! ChatMessage)
For more information, here is the code for the swift class "ChatMessage" that I am trying to cast too.
Import UIKit
Import Foundation
class ChatMessage: NSObject, SINMessage {
var messageId: String
var recipientIds: [AnyObject]
var senderId: String
var text: String
var headers: [NSObject : AnyObject]
var timestamp: NSDate
}
(Note, "Import Sinch" does not need to be in the above listed "ChatMessage" class because I have it in the Bridging-Header)
So, what am I missing/doing wrong?
you should use SINOutgoing instead of sinMessage, you dont need to implement the sinmessage protocoll you self.
Edit:
So instead of trying to parse the notificationm pass push to the sinch client adn react to incoming message,
id<SINClient> client; // get previously created client
[client relayRemotePushNotification:userInfo];
https://www.sinch.com/docs/instant-message/ios/#localandremotepushnotifications
Okay...
So after days of testing workarounds, I found my solution.
The issue seemed to be that, like bolnad suggested, the "SIN" type was not fitting the requirements of the "ChatMessage" class.
So, rather than trying to pass the entire array through a NSNotification variable, I broke it out as follows and set them as NSDefaults
let prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()
chatMessage.senderId = prefs.valueForKey("SENDERID") as! String
chatMessage.text = prefs.valueForKey("TEXT") as! String
chatMessage.messageId = prefs.valueForKey("MESSAGEID") as! String
chatMessage.timestamp = prefs.valueForKey("TIMESTAMP") as! NSDate
chatMessage.recipientIds = prefs.valueForKey("RECIPIENTID") as! [AnyObject]!
self.messageArray.append(chatMessage)
self.historicalMessagesTableView.reloadData()
self.scrollTableToBottom()
In doing so, I was able to satisfy the "ChatMessage" class without issue!
Thanks to everyone who assisted.

Passing Dictionary to Watch

I'm trying to pass data from iPhone -> Watch via Watch Connectivity using background transfer via Application Context method.
iPhone TableViewController
private func configureWCSession() {
session?.delegate = self;
session?.activateSession()
print("Configured WC Session")
}
func getParsePassData () {
let gmtTime = NSDate()
// Query Parse
let query = PFQuery(className: "data")
query.whereKey("dateGame", greaterThanOrEqualTo: gmtTime)
query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]?, error:NSError?) -> Void in
if error == nil
{
if let objectsFromParse = objects as? [PFObject]{
for MatchupObject in objectsFromParse
{
let matchupDict = ["matchupSaved" : MatchupObject]
do {
try self.session?.updateApplicationContext(matchupDict)
print("getParsePassData iPhone")
} catch {
print("error")
}
}
}
}
}
}
I'm getting error twice printed in the log (I have two matchups in Parse so maybe it knows there's two objects and thats why its throwing two errors too?):
Configured WC Session
error
error
So I haven't even gotten to the point where I can print it in the Watch app to see if the matchups passed correctly.
Watch InterfaceController:
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
let matchupWatch = applicationContext["matchupSaved"] as? String
print("Matchups: %#", matchupWatch)
}
Any ideas? Will post any extra code that you need. Thanks!
EDIT 1:
Per EridB answer, I tried adding encoding into getParsePassData
func getParsePassData () {
let gmtTime = NSDate()
// Query Parse
let query = PFQuery(className: "data")
query.whereKey("dateGame", greaterThanOrEqualTo: gmtTime)
query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]?, error:NSError?) -> Void in
if error == nil
{
if let objectsFromParse = objects as? [PFObject]{
for MatchupObject in objectsFromParse
{
let data = NSKeyedArchiver.archivedDataWithRootObject(MatchupObject)
let matchupDict = ["matchupSaved" : data]
do {
try self.session?.updateApplicationContext(matchupDict)
print("getParsePassData iPhone")
} catch {
print("error")
}
}
}
}
}
}
But get this in the log:
-[PFObject encodeWithCoder:]: unrecognized selector sent to instance 0x7fbe80d43f30
*** -[NSKeyedArchiver dealloc]: warning: NSKeyedArchiver deallocated without having had -finishEncoding called on it.
EDIT 2:
Per EridB answer, I also tried just pasting the function into my code:
func sendObjectToWatch(object: NSObject) {
//Archiving
let data = NSKeyedArchiver.archivedDataWithRootObject(MatchupObject)
//Putting it in the dictionary
let matchupDict = ["matchupSaved" : data]
//Send the matchupDict via WCSession
self.session?.updateApplicationContext(matchupDict)
}
But get this error on the first line of the function:
"Use of unresolved identifer MatchupObject"
I'm sure I must not be understanding how to use EridB's answer correctly.
EDIT 3:
NSCoder methods:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
//super.init(coder: aDecoder)
configureWCSession()
// Configure the PFQueryTableView
self.parseClassName = "data"
self.textKey = "matchup"
self.pullToRefreshEnabled = true
self.paginationEnabled = false
}
Error
You are getting that error, because you are putting a NSObject (MatchupObject) which does not conform to NSCoding inside the dictionary that you are going to pass.
From Apple Docs
For most types of transfers, you provide an NSDictionary object with
the data you want to send. The keys and values of your dictionary must
all be property list types, because the data must be serialized and
sent wirelessly. (If you need to include types that are not property
list types, package them in an NSData object or write them to a file
before sending them.) In addition, the dictionaries you send should be
compact and contain only the data you really need. Keeping your
dictionaries small ensures that they are transmitted quickly and do
not consume too much power on both devices.
Details
You need to archive your NSObject's to NSData and then put it in the NSDictionary. If you archive a NSObject which does not conform to NSCoding, the NSData will be nil.
This example greatly shows how to conform a NSObject to NSCoding, and if you implement these things then you just follow the code below:
//Send the dictionary to the watch
func sendObjectToWatch(object: NSObject) {
//Archiving
let data = NSKeyedArchiver.archivedDataWithRootObject(MatchupObject)
//Putting it in the dictionary
let matchupDict = ["matchupSaved" : data]
//Send the matchupDict via WCSession
self.session?.updateApplicationContext(matchupDict)
}
//When receiving object from the other side unarchive it and get the object back
func objectFromData(dictionary: NSDictionary) -> MatchupObject {
//Load the archived object from received dictionary
let data = dictionary["matchupSaved"]
//Deserialize data to MatchupObject
let matchUpObject = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! MatchupObject
return matchUpObject
}
Since you are using Parse, modifying an object maybe cannot be done (I haven't used Parse in a while, so IDK for sure), but from their forum I found this question: https://parse.com/questions/is-there-a-way-to-serialize-a-parse-object-to-a-plain-string-or-a-json-string which can help you solve this problem easier than it looks above :)

Swift: Error updating UI component when using a delegate

I'm trying to update a progress bar with the progress of loading a load of values into CoreData. However, whenever I try to call an update on my progressView component, I get a fatal error stating that "unexpectedly found nil while unwrapping an Optional value".
The interesting thing is that this happens even if I put 'self.progressView.progress = 0.5' in the delegate method of my program - indicating that it's the progressView component it can't find rather than an issue with the value. A quick check with println also confirms the value does exist and so isn't nil. Note that if I put the 'self.progressView.progress = 0.5' statement under a function connected directly to a button, it works fine so it must be some sort of issue with the command being called from the delegate.
Can anyone work out what I'm doing wrong here? Thanks for your help.
Delegate method:
class ViewControllerUpdate: UIViewController, NSURLSessionDelegate, NSURLSessionDownloadDelegate, saveUpdate {
[....]
func updateStatus(status: String, progress: Float?) {
if let percentProgress = progress? {
self.progressView.progress = 0.5
}
//println(progress) - NOTE THIS IS CORRECTLY POPULATED WITH THE APPROPRIATE VALUE
}
Calling class:
protocol saveUpdate {
func updateStatus(status:String, progress:Float?)
}
class sqlPullSave {
let classtoUpdate: saveUpdate = ViewControllerUpdate()
func saveTSVtoSQL(fromFile: NSURL) -> Int {
//Load up the information into a Dictionary (tsv)
//let tsvURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(fromFileName, ofType: fromFileExtension)!)
let tsvURL: NSURL = fromFile
let tab = NSCharacterSet(charactersInString: "\t")
let tsv = CSV(contentsOfURL: tsvURL, separator: tab)
//let defResult: AnyObject = tsv.rows[0]["Name"]!
//let tryagain:String = AnyObjecttoString(tsv.rows[1]["Name"]!)
//load the data into the SQLite database...
dispatch_async(dispatch_get_main_queue()) {
for a in 0..<tsv.rows.count {
self.SQLsaveLine(self.AnyObjecttoString(tsv.rows[a]["Name"]!),
name_l: "",
desc: self.AnyObjecttoString(tsv.rows[a]["1"]!),
jobTitle: self.AnyObjecttoString(tsv.rows[a]["2"]!),
extn: self.AnyObjecttoString(tsv.rows[a]["3"]!)
// update status
var percentComplete: Float = (Float(a) / Float(tsv.rows.count))
self.classtoUpdate.self.updateStatus("SQLload", progress: percentComplete)
}
}
return 0
}

Type 'AnyObject' does not conform to protocol 'SequenceType'

func loadThumbnails() {
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let documentsDirectory:NSString = paths[0] as NSString
var error:NSError?
let fileManager = NSFileManager()
let directoryContent:AnyObject = fileManager.contentsOfDirectoryAtPath(documentsDirectory, error: &error)!
thumbnails = [QSPhotoInfo]()
for item:AnyObject in directoryContent {
let fileName = item as NSString
if fileName.hasPrefix(kThumbnailImagePrefix) {
let image = loadImageFromDocumentsDirectory(fileName)
var photoInfo = QSPhotoInfo()
photoInfo.thumbnail = image;
photoInfo.thumbnailFileName = fileName
thumbnails += photoInfo
}
}
}
the compile error is below:
Type 'AnyObject' does not conform to protocol 'SequenceType'
what does this menas?
who can help me ,thks a lot!!!!
Apple states in The Swift Programming Language:
The for-in loop performs a set of statements for each item in a range,
sequence, collection, or progression.
Right now, directoryContent is just conforming to protocol AnyObject, so you can't use for loops over it. If you want to do so, you have to do something similar to the following:
for item in directoryContent as [AnyObject] {
//Do stuff
}
contentsOfDirectoryAtPath returns an NSArray, whereas you are casting it to AnyObject. The solution is to cast it to either [AnyObject]? or NSArray:
let directoryContent: [AnyObject]? = fileManager.contentsOfDirectoryAtPath(documentsDirectory, error: &error)
or
let directoryContent: NSArray? = fileManager.contentsOfDirectoryAtPath(documentsDirectory, error: &error)
Then use an optional binding before the for loop:
if let directoryContent = directoryContent {
for item:AnyObject in directoryContent {
Looking at the contentsOfDirectoryAtPath documentation, it states it always returns an array - so what said above can be reduced to unwrapping the return value to either a swift or objc array, with no need to use the optional binding:
let directoryContent: [AnyObject] = fileManager.contentsOfDirectoryAtPath(documentsDirectory, error: &error)!
or
let directoryContent: NSArray = fileManager.contentsOfDirectoryAtPath(documentsDirectory, error: &error)!

Creating NSData from NSString in Swift

I'm trying to ultimately have an NSMutableURLRequest with a valid HTTPBody, but I can't seem to get my string data (coming from a UITextField) into a usable NSData object.
I've seen this method for going the other way:
NSString(data data: NSData!, encoding encoding: UInt)
But I can't seem to find any documentation for my use case. I'm open to putting the string into some other type if necessary, but none of the initialization options for NSData using Swift seem to be what I'm looking for.
In Swift 3
let data = string.data(using: .utf8)
In Swift 2 (or if you already have a NSString instance)
let data = string.dataUsingEncoding(NSUTF8StringEncoding)
In Swift 1 (or if you have a swift String):
let data = (string as NSString).dataUsingEncoding(NSUTF8StringEncoding)
Also note that data is an Optional<NSData> (since the conversion might fail), so you'll need to unwrap it before using it, for instance:
if let d = data {
println(d)
}
Swift 4 & 3
Creating Data object from String object has been changed in Swift 3. Correct version now is:
let data = "any string".data(using: .utf8)
In swift 5
let data = Data(YourString.utf8)
Here very simple method
let data = string.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
Swift 4
let data = myStringVariable.data(using: String.Encoding.utf8.rawValue)
// Checking the format
var urlString: NSString = NSString(data: jsonData, encoding: NSUTF8StringEncoding)
// Convert your data and set your request's HTTPBody property
var stringData: NSString = NSString(string: "jsonRequest=\(urlString)")
var requestBodyData: NSData = stringData.dataUsingEncoding(NSUTF8StringEncoding)!
To create not optional data I recommend using it:
let key = "1234567"
let keyData = Data(key.utf8)
Convert String to Data
extension String {
func toData() -> Data {
return Data(self.utf8)
}
}
Convert Data to String
extension Data {
func toString() -> String {
return String(decoding: self, as: UTF8.self)
}
}
Swift 4.2
let data = yourString.data(using: .utf8, allowLossyConversion: true)

Resources