I am trying to center CoreText, and the best example I found was in this blog post. However, I was working with NSAttributedString, and although the CFMutableAttributedStringReference should be toll-free bridged to NSMutableAttributedString, this doesn't seem to work any bridging magic when I try to introduce the following in my drawRect method:
NSMutableAttributedString* attString = [[NSMutableAttributedString alloc]
initWithString:[NSString stringWithFormat:#"%d %#", self.currentNum, self.units]]; //2
// set paragraph style attribute
CFAttributedStringSetAttribute(attString, CFRangeMake(0, CFAttributedStringGetLength(attrStr)), kCTParagraphStyleAttributeName, paragraphStyle);
// set font attribute
CFAttributedStringSetAttribute(attString, CFRangeMake(0, CFAttributedStringGetLength(attrStr)), kCTFontAttributeName, font);
In particular, I am getting the same build error for both of the last two lines:
Incompatible pointer types passing retainable parameter of type 'NSMutableAttributedString'__strong to a CF function expecting 'CFMutableAttributedStringRef' (aka 'struct __CFAttributedString *') type.
I get that toll-free bridging doesn't have to go backwards, but then how can I set the alignment of my NSMutableAttributedString to center it in the provided frame? I don't know the text or size of the text in advance, so I need to be able to center it so it looks alright in a variety of sizes.
Thanks for any suggestions.
You have to cast toll free bridged types as documented in Casting and Object Lifetime Semantics.
Quote:
Through toll-free bridging, in a method where you see for example an NSLocale * parameter, you can pass a CFLocaleRef, and in a function where you see a CFLocaleRef parameter, you can pass an NSLocale instance. You also have to provide other information for the compiler: first, you have to cast one type to the other; in addition, you may have to indicate the object lifetime semantics.
Example:
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attString, โฆ
Alternatively you can use NSMutableAttributedString's methods to add attributes.
Related
I have had exactly 10 days of iOS/Objective-C training (and pretty much no other coding classes) and am thus way out of my league on this, but I inherited a huge iOS app at work for which I am now responsible for upgrading from iOS6-centric to iOS 7-centric. I'm trying to clean up all of the warnings in Xcode and simply cannot figure this one out. I've searched for days and read every answer here on SO, but none exactly answers my question (though some have helped me get closer, for which I am truly grateful).
I know that "sizeWithFont:constrainedToSize:lineBreakMode:" is deprecated and needs to be replaced with "boundingRectWithSize:options:attributes:context:", but for the life of me I can't figure out how to convert some existing code from the old method to the new. If I can get this one straightened out it will clear up 35 other warnings in Xcode, as that same deprecated method is used in numerous other places.
The research I've done yields a few examples of how the new method is used, but it appears it is used in different ways (CGRect and CGSize) and Apple's documentation just sends me in "one infinite loop". For my "options:", Apple says to use paragraph style options, yet those are completely different than what was available for the deprecated style (half of those are deprecated, too). For example, they say if you don't specify a style, it will use the default paragraph style, but I don't know what attributes those are or really where to find them to verify what they are. If I DO specify a style, it has to be "this one" or "that one", but then if I use one of those I have to also use "this other one", however none of those will take effect unless my line break mode is "yet another one", but the "yet another one" style isn't the one I need to use.
So, I can't figure out how to specifically translate the attributes I have in the old code into code that will yield the exact same results in the non-deprecated method. I think I'm pretty close, but can't get the correct use of "NSLineBreakByClipping" to translate into the new method's syntax without getting a hard error. The error is in the line "width=expectedLabelSize1.width" and says "No member named "width" in 'struct CGRect' ". If I change the CGRect so CGSize, like it was in the original code, I get a different error on that specific line about initializing an expression with an incompatible type.
Here is the original code:
int width = 0;
if([surveyType isEqualToString:#"Site Survey"]){
//calculate the expected width of the survey label...
CGSize maximumLabelSize = CGSizeMake(165,16);
CGSize expectedLabelSize1 = [surveyName sizeWithFont:[UIFont systemFontOfSize:9.0] constrainedToSize:maximumLabelSize lineBreakMode:NSLineBreakByClipping];
width=expectedLabelSize1.width;
if(width > 165){
width=165;
}
And here is what I've been able to cobble together instead:
//calculate the expected width of the survey label...
CGSize maximumLabelSize = CGSizeMake(165,16);
CGRect expectedLabelSize1 = [surveyName boundingRectWithSize:maximumLabelSize
options:(NSStringDrawingUsesLineFragmentOrigin |
NSStringDrawingTruncatesLastVisibleLine | NSLineBreakByWordWrapping)
attributes:#{NSFontAttributeName: [UIFont systemFontOfSize:9.0]}
context:nil];
width=expectedLabelSize1.width;
if(width > 165){
width=165;
}
I don't really know what I'm doing here, obviously, so I'm hoping someone can show me how to convert the old method to the new and not lose any of the functionality or formatting in the process. How can I incorporate the NSLineBreakModeByClipping attribute I originally had if that no longer appears to be a available attribute or option?
THANK YOU!!!
EDIT: I apologize, but I just realized that I left out some code that may help explain one of the errors I was getting. I left out the very first two lines above, where the variable "width" is initialized as an integer. This whole "label size calculation" code is part of a much larger "if" statement, but the only part giving me fits is the deprecated method to which this post pertains. Anyway, since "width" is not addressed in the CGRect, the very next line after the CGRect method (width=expectedLabelSize1.width) generates the error above about "width" not being a member of the struct. I get that, now, but I don't know how to add the "width" attribute to the CGRect struct. The overall method (boundingRectWithSize:options:attributes:context:) as I have it appears to be "clean", in that it doesn't generate any errors on its own, but it doesn't address "width" or NSLineBreakModeByClipping. That's where I'm getting an error (for the missing "width" variable) and where I'm getting lost on exactly how to incorporate NSLineBreakModeByClipping into the new method syntax.
It is simpler to use UILabel sizeThatFits
eg.
CGSize maximumLabelSize = CGSizeMake(CGFLOAT_MAX,16);
CGSize expectedLabelSize1 = [surveyName sizeThatFits:max];
width=expectedLabelSize1.width;
if(width > 165){
width=165;
}
Also see http://doing-it-wrong.mikeweller.com/2012/07/youre-doing-it-wrong-2-sizing-labels.html
Here's how I perform my text sizing these days:
- (CGSize)textSizeWithBoundingWidth:(CGFloat)boundingWidth attributes:(NSDictionary *)attributes
{
CGSize boundingSize = (CGSize){.width = boundingWidth,.height = 0};
NSStringDrawingOptions options = (NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine);
CGRect textBoundingRect = [self boundingRectWithSize:boundingSize options:options attributes:attributes context:nil];
return ((CGSize){.width = CGRectGetMaxX(rect),.height = CGRectGetMaxY(rect)})
}
You will also need to pass in a dictionary of text attributes. For the two you want (font and lineBreakMode), you can pass them in like so:
NSMutableDictionary* attributesDictionary = [NSMutableDictionary dictionary];
[attributesDictionary setObject:self.font forKey:NSFontAttributeName];
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[style setLineBreakMode:self.lineBreakMode];
[attributesDictionary setObject:style forKey:NSParagraphStyleAttributeName];
The documentation for kCTUnderlineStyleAttributeName relays the following:
kCTUnderlineStyleAttributeName
The style of underlining, to be applied at render time, for the text to which this attribute applies. Value must be a CFNumber object. Default is kCTUnderlineStyleNone. Set a value of something other than kCTUnderlineStyleNone to draw an underline. In addition, the constants listed in CTUnderlineStyleModifiers can be used to modify the look of the underline. The underline color is determined by the text's foreground color.
The signature for the setAttributes function is as follows:
func setAttributes(attrs: [NSObject : AnyObject]?, range: NSRange)
The issue that I'm having is that the documentation seems to allude to the fact that CTUnderlineStyle.Single can (and should, in Swift) be used as a value for the kCTUnderlineStyleAttributeName key. However, the former is a struct, and as a result does not conform to the AnyObject protocol required by the dictionaries value type.
Any ideas?
I spend quite a few time on this.
The value needs to confront the AnyObject protocol.
Instead of using CTUnderlineStyle.Single use NSNumber like this:
NSNumber(int: CTUnderlineStyle.Single.rawValue)
Example:
attributedString.addAttribute(kCTUnderlineStyleAttributeName, value:NSNumber(int: CTUnderlineStyle.Single.rawValue), range:NSRange(location:0,length:attributedString.length));
in swift 3 worked for me this code
let attributes:[AnyHashable : Any] = [kCTUnderlineStyleAttributeName as AnyHashable:NSNumber(value:CTUnderlineStyle.single.rawValue)]
The iOS SpeakHere example code has a pair of methods in Class SpeakHereController, stopRecord and record that respectively save and initialize a file for saving the recording. The two methods handle the filename string slightly differently as you can see in the following two lines of code in those methods.
recordFilePath = (CFStringRef)[NSTemporaryDirectory() stringByAppendingPathComponent: #"recordedFile.caf"];
recorder->StartRecord(CFSTR("recordedFile.caf"));
The string "recordedFile.caf" occurs once preceded by an # sign and once without. I am planning on using the following NSString constuct to produce filename, but I don't know how to use the result correctly in the two places mentioned in this paragraph. So my question is how to use the constructed string filename in those lines?
#property int counter;
NSString *filename = [[NSString alloc] initWithFormat:#"recordedFile%d.caf",self.counter];
try
recorder->StartRecord(CFSTR([filename UTF8String]));
The difference is between a C API for string objects (CFStringRef in Core Foundation) vs. an Objective-C API for string objects (NSString in Cocoa).
The compiler knows that #"..." is an Objective-C string literal and constructs a static instance of (a private subclass of) NSString.
The compiler doesn't exactly have a similar native knowledge of CFString literals. Instead, Apple's headers define the CFSTR() preprocessor macro to wrap a C-style string in a Core Foundation string. The argument passed to the macro must be a C string literal (e.g. "foo"). The syntax in the other answer which passes a run-time expression returning a non-literal C string pointer not only isn't correct, I don't believe it can compile. Depending on the compiler, CFSTR() may produce a true compile-time CFString object rather than being a run-time expression.
So: #"recordedFile.caf" is an NSString literal, CFSTR("recordedFile.caf") is a C string literal turned into a CFStringRef. You should just think of it as a CFString literal.
Happily, Apple designed Core Foundation and Cocoa so that NSString and CFString are toll-free bridged. They are, at a certain level of abstraction, the same sort of object. You can just type-cast between the two types freely. With ARC, such a type cast requires a bridge that lets ARC know about the memory management of the change.
So, you can do:
CFStringRef cfstring = CFSTR("recordedFile.caf");
NSString* nsstring = (__bridge NSString*)cfstring;
Or:
NSString* nsstring = #"recordedFile.caf";
CFStringRef cfstring = (__bridge CFStringRef)nsstring;
For your particular case:
recorder->StartRecord((__bridge CFStringRef)filename);
With the introduction of Swift I've been trying to get my head round the new language
I'm an iOS developer and would use types such as NSString, NSInteger, NSDictionary in an application. I've noticed that in the "The Swift Programming Language" ebook by Apple, they use the Swift types String, Int, Dictionary
I've noticed the Swift types don't have (or are differently named) some of the functions that the Foundation types do. For example NSString has a length property. But I've not been able to find a similar one for the Swift String.
I'm wondering, for an iOS application should I still be using the Foundation types?
You should use the Swift native types whenever possible. The language is optimized to use them, and most of the functionality is bridged between the native types and the Foundation types.
While String and NSString are mostly interchangeable, i.e, you can pass String variables into methods that take NSString parameters and vice versa, some methods seem to not be automatically bridged as of this moment. See this answer for a discussion on how to get the a String's length and this answer for a discussion on using containsString() to check for substrings. (Disclaimer: I'm the author for both of these answers)
I haven't fully explored other data types, but I assume some version of what was stated above will also hold true for Array/NSArray, Dictionary/NSDictionary, and the various number types in Swift and NSNumber
Whenever you need to use one of the Foundation types, you can either use them to type variables/constants explicitly, as in var str: NSString = "An NSString" or use bridgeToObjectiveC() on an existing variable/constant of a Swift type, as in str.bridgeToObjectiveC().length for example. You can also cast a String to an NSString by using str as NSString.
However, the necessity for these techniques to explicitly use the Foundation types, or at least some of them, may be obsolete in the future, since from what is stated in the language reference, the String/NSString bridge, for example, should be completely seamless.
For a thorough discussion on the subject, refer to Using Swift with Cocoa and Objective-C: Working with Cocoa Data Types
NSString : Creates objects that resides in heap and always passed by reference.
String: Its a value type whenever we pass it , its passed by value.
like Struct and Enum, String itself a Struct in Swift.
public struct String {
// string implementation
}
But copy is not created when you pass. It creates copy when you first mutate it.
String is automatically bridged to Objective-C as NSString. If the Swift Standard Library does not have, you need import the Foundation framework to get access to methods defined by NSString.
Swift String is very powerful it has plethora of inbuilt functions.
Initialisation on String:
var emptyString = "" // Empty (Mutable)
let anotherString = String() // empty String immutable
let a = String(false) // from boolean: "false"
let d = String(5.999) // " Double "5.99"
let e = String(555) // " Int "555"
// New in Swift 4.2
let hexString = String(278, radix: 18, uppercase: true) // "F8"
create String from repeating values:
let repeatingString = String(repeating:"123", count:2) // "123123"
In Swift 4 -> Strings Are Collection Of Characters:
Now String is capable of performing all operations which anyone can
perform on Collection type.
For more information please refer apple documents.
Your best bet is to use Swift native types and classes, as some others have noted NSString has toll free translation to String, however, they're not the same a 100%, take for example the following
var nsstring: NSString = "\U0001F496"
var string: String = "\U0001F496"
nsstring.length
count(string)
you need to use the method count() to count the characters in string, also note that nsstring.length returns 2, because it counts its length based on UTF16.
Similar, YES
The same, NO
String and NSString are interchangeable, so it doesn't really matter which one you use. You can always cast between the two, using
let s = "hello" as NSString
or even
let s: NSString = "hello"
NSInteger is just an alias for an int or a long (depending on the architecture), so I'd just use Int.
NSDictionary is a different matter, since Dictionary is a completely separate implementation.
In general I'd stick to swift types whenever possibile and you can always convert between the two at need, using the bridgeToObjectiveC() method provided by swift classes.
Swift 4 update
String gets revisions in swift 4. Now you can directly call count on it and it consider grapheme clusters as 1 piece, like an emoji. NSString is not updated and is counting it in another way.
var nsstring: NSString = "๐ฉโ๐ฉโ๐งโ๐ฆ"
var string: String = "๐ฉโ๐ฉโ๐งโ๐ฆ"
print(nsstring.length) // 11
print(string.count) // 1
Since the objective C types are still dynamically dispatched they're probably going to be slower. I'd say you're best served using the Swift native types unless you need to interact with objective-c APIs
Use the Swift native types whenever you can. In the case of String, however, you have "seamless" access to all the NSString methods like this:
var greeting = "Hello!"
var len = (greeting as NSString).length
Swift Strings are quite elegant and easy to use, unless you need to parse them. The whole concept of indexing into Swift strings is just plain crazy. Whenever I need to parse through a typical unicode string, I convert it to NSString. Swift makes this a bit tricky though in that the common "character" integer expressions like ' ' or 'A' or '0' don't work in Swift. You have to use 32, 65, 48. Unfortunately, I'm not kidding! Because of this, I've put most of my string parsing code into an NSString extension written in Objective-C.
Yes I do know WHY Swift's designers made String indexing so crazy: They wanted to be able to express many-byte characters like emoji as single "Characters". My choice would have been to let this rare use case be expressed as multiple UTF16 characters, but what the heck.
String is a struct
// in Swift Module
public struct String
{
}
NSString is a class
// in Foundation Module
open class NSString : NSObject
{
}
I have a piece of code that the ARC converter turned into this...
// firstRange is a NSRange obviously
// test is an NSString * passed in as parameter to the method
NSRange range = NSMakeRange(firstRange.location, (lastRange.location - firstRange.location) + lastRange.length);
NSString *sentence = [text substringWithRange:range];
// OK, now chop it up with the better parser
CFRange allTextRange = CFRangeMake(0, [sentence length]);
CFLocaleRef locale = CFLocaleCopyCurrent();
CFStringTokenizerRef tokenizer = CFStringTokenizerCreate(kCFAllocatorDefault,
(__bridge CFStringRef) sentence,
allTextRange,
kCFStringTokenizerUnitWord,
locale);
I call this A LOT and I suspect that it leaks somehow. Is that CFStringTokenizerCreate call kosher? I am especially suspicious of the __bridge call. Do I create an intermediate that I have to manually release or some such evil?
You need to CFRelease the tokenizer and locale or else they will leak.
This falls under Core Foundation Ownership Policy and has nothing to do with ARC.
The __bridge cast tells ARC that no ownership transfer is done for sentence in CFStringTokenizerCreate call. So that is Ok.
You can test for memory leaks with Xcode's static analyser and profiler.
You need to call CFRelease(tokenizer); when you are done using the tokenizer. See Ownership Policy. You should call CFRelease(locale); too.
Your __bridge sentence syntax is correct. I must say that Xcode is correct about __bridge and __bridge_transfer most of the time. In your case, you are passing a reference of NSObject for use with CF. You have no intention to transfer the ownership to CF because you think ARC is great at managing NSObjects. So when CFStringTokenizerCreate is done using sentence, it won't do anything to free it up. ARC will then free up sentence.
On the other hand, if you changed it to __bridge_transfer, you are telling ARC that you are transferring the ownership to CF. Therefore, when you are done, ARC won't free up sentence. You must call CFRelease(sentence); to free it up, which is not a desired behavior.
My gut tells me that it should be __bridge_transfer instead of bridge since you are calling create (unless there is a CFRelease call later). I also think the locale needs to be released since it is a copy.
EDIT Oops ignore me, I read it wrong (was using a phone)
For any Swift users reading this thread: the CFRelease() function does not seem to have been carried on into the Swift language, as Core Foundation objects are automatically memory managed (according to a compiler warning I'm seeing in Swift 3.0.2), so that's one less thing to think about.