again convert textKit from Objective-C to Swift - objective-c

The following code was used in my app to change the state for text in a textview with strikeThrough. Now i wrote a small sample-app, in Objective-C and Swift. Again the result is frustrating as u can see in the screenshots. Any help is welcome so much.
I just use a TextView and try to show some text with StrikeThrough-Layout (other styles like Bold, Italic, Underline... have the same result)
First objc, that is ok, although the font-size of the striked part is very small
and now with Swith. The font is small as with objc, but there is no strikethrough :-)
And now again (dont know another way) the test-code:
objc Part 1: set the Font for a Range and call makeStrikeThrough()
- (void) setFont
{
NSRange range = NSMakeRange(11, 24);
[self makeStrikeThrough:range];
}
same in swift:
func setFont() {
let range = NSMakeRange(11, 24)
self.makeStrikeThrough(range)
}
objc Part 2: the strikeThrough
- (void) makeStrikeThrough:(NSRange)selectedRange
{
NSMutableDictionary *dict = [self getDict:selectedRange];
[_textView.textStorage beginEditing];
[_textView.textStorage setAttributes:dict range:selectedRange];
[_textView.textStorage endEditing];
}
and in Swift:
func makeStrikeThrough(selectedRange: NSRange) {
let dict = self.getDict(selectedRange)
self.textView.textStorage.beginEditing()
textView.textStorage.setAttributes([String() : dict], range: selectedRange)
self.textView.textStorage.endEditing()
}
objc Part 3: the help-method getDict() to buid a dictionary with the StrikeThrough
- (NSMutableDictionary*) getDict:(NSRange)range
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:[NSNumber numberWithInt:2] forKey:NSStrikethroughStyleAttributeName];
return dict;
}
and again in Swift
func getDict(range: NSRange) -> NSMutableDictionary {
let dict = NSMutableDictionary()
dict[NSStrikethroughStyleAttributeName] = NSNumber(integer: NSUnderlineStyle.StyleDouble.rawValue)
return dict
}
I´ve tried to reduce the problem to the root. Perhaps u vote me down ;-)
But i need a solution...
Or should i use AttributedStrings?
The procts to download
objc
swift

It boils down to one line of code in your Swift function:
func makeStrikeThrough(selectedRange: NSRange) {
let dict = self.getDict(selectedRange)
self.textView.textStorage.beginEditing()
textView.textStorage.setAttributes([String() : dict], range: selectedRange) // error
self.textView.textStorage.endEditing()
}
it should have been just dict:
func makeStrikeThrough(selectedRange: NSRange) {
let dict = self.getDict(selectedRange)
self.textView.textStorage.beginEditing()
textView.textStorage.setAttributes(dict, range: selectedRange)
self.textView.textStorage.endEditing()
}
And you need to change your getDict function too:
func getDict() -> [String: AnyObject] {
return [NSStrikethroughStyleAttributeName: 2]
}

Related

Creating Font Descriptors with Fractions in Objective C

I am having trouble displaying fractions in Objective C, even though the equivalent code works in Swift. I must be missing something very obvious??
Swift Code:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//: Playground - noun: a place where people can play
let pointSize = self.label.font.pointSize
let systemFontDesc = UIFont.systemFont(ofSize: pointSize, weight: UIFontWeightLight).fontDescriptor
let fractionFontDesc = systemFontDesc.addingAttributes(
[
UIFontDescriptorFeatureSettingsAttribute: [
[
UIFontFeatureTypeIdentifierKey: kFractionsType,
UIFontFeatureSelectorIdentifierKey: kDiagonalFractionsSelector,
], ]
] )
print(fractionFontDesc)
self.label.font = UIFont(descriptor: fractionFontDesc, size:pointSize)
print("label.font.descriptor: \(self.label.font.fontDescriptor)")
}
Result:
equivalent code in Objective C
-(void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
CGFloat pointSize = _label.font.pointSize;
UIFontDescriptor *systemFontDesc = [UIFont systemFontOfSize:pointSize weight:UIFontWeightLight].fontDescriptor;
UIFontDescriptor *fractionDescriptor = [systemFontDesc fontDescriptorByAddingAttributes:#{ UIFontDescriptorFeatureSettingsAttribute : #{
UIFontFeatureTypeIdentifierKey: #(11), // kFractionsType
UIFontFeatureSelectorIdentifierKey: #(2)}}]; // kDiagonalFractionsSelector
NSLog(#"%#\n\n", fractionDescriptor);
UIFont *fracFont = [UIFont fontWithDescriptor:fractionDescriptor size:pointSize];
NSLog(#"fracFont.fontDescriptor: %#\n\n", fracFont.fontDescriptor);
[_label setFont: fracFont];
NSLog(#"label.font.descriptor: %#\n\n", _label.font.fontDescriptor);
}
Result:
The problem is the expression
fontDescriptorByAddingAttributes:#{
UIFontDescriptorFeatureSettingsAttribute : #{
That last #{ indicates that your UIFontDescriptorFeatureSettingsAttribute is a dictionary. That is wrong. It needs to be an array of dictionaries. (Look carefully at your original Swift code and you will see that this is so.)
You would do much better, in my opinion, to form your dictionary in one line, make an array of it in another line, and call fontDescriptorByAddingAttributes in a third line. That way you'll be clear on what you're doing. Right now you're just confusing the heck out of yourself with all those nested literals...

Swift's "if let" equivalent in Objective C

What would be "if let" equivalent in Objective C? The example snippet I want to convert to Objective C is below;
if let pfobjects = images as? [PFObject] {
if pfobjects.count > 0 {
var imageView: PFImageView = PFImageView()
imageView.file = pfobjects[0] as! PFFile
imageView.loadInBackground()
}
}
There's no direct equivalent to if let in Objective-C, because if let does Swift-specific things (unwrapping optionals and rebinding identifiers) that don't have direct equivalents in Objective-C.
Here's a nearly equivalent of your Swift code:
if (images != nil) {
NSArray<PFObject *> *pfobjects = (id)images;
if (pfobjects.count > 0) {
PFImageView *imageView = [[PFImageView alloc] init];
assert([pfobjects[0] isKindOfClass:[PFFile class]]);
imageView.file = (PFFile *)pfobjects[0];
[imageView loadInBackground];
}
}
But this Objective-C code won't verify that images only contains instances of PFObject, and should successfully create an image view as long as pfobjects[0] is a PFFile. Your Swift code will do nothing (create no image view) if images contains any non-PFObject elements.
You can use NSPredicate to verify the array contains only instances of PFObject:
NSPredicate *p = [NSPredicate predicateWithFormat:#"self isKindOfClass: %#", [PFObject class]];
NSInteger numberThatArePFObjects = [images filteredArrayUsingPredicate:p].count;
if(numberThatArePFObjects && numberThatArePFObjects == images.count){
// certain that images only contains instances of PFObject.
}
If however you weren't working with an array but a single object then it is simpler:
if([image isKindOfClass:[PFObject class]]){
// certain that image is a valid PFObject.
}
Or if you wanted a new variable:
PFObject* obj = nil;
if([image isKindOfClass:[PFObject class]] && (obj = image)){
// certain that obj is a valid PFObject.
}
You can use something like this:
NSArray<PFObject *> *pfobjects;
if ([images isKindOfClass: [NSArray<PFObject> class]] && (pfobjects = images)) {
// your code here
}
You want three things simultaneously. Let's split them:
variable as? OtherType is possible, but erases type, because it returns id. Implementation is as easy as a category on NSObject class, so it becomes NSArray *array = [jsonDict[#"objects"] ifKindOfClass:NSArray.class].
Implementation
#interface NSObject (OptionalDowncast)
- (id)ifKindOfClass:(__unsafe_unretained Class)clazz;
#end
#implementation NSObject (OptionalDowncast)
- (id)ifKindOfClass:(__unsafe_unretained Class)clazz {
return [self isKindOfClass:clazz] ? self : nil;
}
#end
if let is also possible in Objective-C if type is known, so it cannot be combined with previous thing. Easiest way is: for(NSArray *array = [self getItems]; array != nil; array = nil) { ... }, but if you want to use else branch, it gets a bit more complex. I have made SwiftyObjC pod for that, please take a look
Check generic template is not possible during type cast in Objective-C, thus you can cast to NSArray, but you can't cast to NSArray<PFObject>
I don't see iterations over your array: With all that being said, I think best example is (assuming images is an array already):
for(PFFile *file = [images.firstObject ifKindOfClass:PFFile.class]; file != nil; file = nil) {
imageView.file = file;
[imageView loadInBackground];
}
If you need to also iterate over it:
for(id object in images) {
for(PFFile *file = [object ifKindOfClass:PFFile.class]; file != nil; file = nil) {
//operate on file
}
}
You can use Objective-C++ in place of Objective-C. In this you can use the next define:
#define let const auto
Note: it is not the same exactly (Swift has wrapped values, ...) but it makes the work easier.
And through of this define you can use it of this way:
if (let pfobjects = images) {
if (pfobjects.count > 0 {
let imageView = [[PFImageView alloc] init];
imageView.file = pfobjects[0];
imageView loadInBackground();
}
}
To convert your Objective-C class in Objective-C++ class you only must change the extension of implementation file of .m to .mm

Append to NSTextView and scroll

OK, what I need should have been very simple. However, I've looked everywhere and I'm not sure I've found something that works 100% (and it's something that has troubled me in the past too).
So, here we are :
I want to be able to append to an NSTextView
After appending, the NSTextView should scroll down (so that that latest appended contents are visible)
Rather straightforward, huh?
So... any ideas? (A code example that performs exactly this simple "trick" would be more than ideal...)
After cross-referencing several answers and sources (with some tweaks), here's the answer that does work (given _myTextView is an NSTextView outlet) :
- (void)appendToMyTextView:(NSString*)text
{
dispatch_async(dispatch_get_main_queue(), ^{
NSAttributedString* attr = [[NSAttributedString alloc] initWithString:text];
[[_myTextView textStorage] appendAttributedString:attr];
[_myTextView scrollRangeToVisible:NSMakeRange([[_myTextView string] length], 0)];
});
}
The appendAttributedString and scrollToEndOfDocument are available starting in OS X 10.0 and 10.6 respectively
extension NSTextView {
func append(string: String) {
self.textStorage?.appendAttributedString(NSAttributedString(string: string))
self.scrollToEndOfDocument(nil)
}
}
Simply use this way :
for (NSInteger i=1; i<=100; i++) {
[self.textView setString:[NSString stringWithFormat:#"%#\n%#",[self.textView string],#(i)]];
}
[self.textView scrollRangeToVisible:NSMakeRange([[self.textView string] length], 0)];
Here's a Swift version of Anoop Vaidya's answer
extension NSTextView {
func append(string: String) {
let oldString = self.string == nil ? "" : self.string!
let newString = NSString(format: "%#%#", oldString, string)
self.string = newString
}
}
Here's a Swiftier solution:
extension NSTextView {
func appendString(string:String) {
self.string! += string
self.scrollRangeToVisible(NSRange(location:countElements(self.string!), length: 0))
}
}

Converting NSArray Contents to a varargs (With ARC) For Use With NSString initWithFormat

We have some code today that takes an NSArray and passes it as a argument list to -[NSString initWithFormat:arguments] and we're trying to get this to work with ARC. Here's the code were using
NSString* format = #"Item %s and Item %s"; // Retrieved elsewhere
NSArray* args = [NSArray arrayWithObjects:#"1", #"2", nil]; // Retrieved elsewhere
// http://cocoawithlove.com/2009/05/variable-argument-lists-in-cocoa.html
char* argsList = (char*) malloc(sizeof(NSString*) * args.count);
[args getObjects:(id*) argsList];
NSString* message = [[[NSString alloc] initWithFormat:format arguments:argsList] autorelease];
free(argsList);
Any recommendations on how to make this ARC compliant? Or we're even open to a better way of doing it.
This only works for arrays with a single element
The answer by chrisco was working well, until I went to compile with 64-bit architecture. This caused an error:
EXC_BAD_ADDRESS type EXC_I386_GPFLT
The solution was to use a slightly different approach for passing the argument list to the method:
+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments;
{
__unsafe_unretained id * argList = (__unsafe_unretained id *) calloc(1UL, sizeof(id) * arguments.count);
for (NSInteger i = 0; i < arguments.count; i++) {
argList[i] = arguments[i];
}
NSString* result = [[NSString alloc] initWithFormat:format, *argList] ;// arguments:(void *) argList];
free (argList);
return result;
}
Cannot find a way to do this obj-c but a swift helper class finally got this working (my whole project is obj-c except this class)
#objc class StringFormat: NSObject {
class func format(key: String, args: [AnyObject]) -> String {
let locArgs: [CVarArgType] = args.map({ (arg: AnyObject) -> CVarArgType in
if let iArg = (arg is NSNumber ? arg.intValue : nil) {
return iArg
}
return arg as! CVarArgType
});
return String(format: key, arguments: locArgs)
}
}
There is some magic going on, to do with how [CVarArgType] doesn't behave like a normal array - but this works in the flexible cross architecture way you expect it to.
Expanding on #mcfedr's answer, this Swift 3 helper does the job:
import Foundation
#objc (FTStringFormat) public class StringFormat: NSObject {
#objc public class func format(key: String, args: [AnyObject]) -> String {
let locArgs: [CVarArg] = args.flatMap({ (arg: AnyObject) -> CVarArg? in
if let arg = arg as? NSNumber {
return arg.intValue
}
if let arg = arg as? CustomStringConvertible {
return arg.description
}
return nil
});
return String(format: key, arguments: locArgs)
}
}
Calling from Objective-C:
[FTStringFormat formatWithKey:#"name: %# age: %d" args:#[#"John", #(42)]]
For the %# format specifier we're using Swift's CustomStringConvertible protocol in order to call description on all of the array members.
Supporting all number format specifiers like %d and %f is not really possible because the NSNumber object doesn't reveal if it's an integer or float. So we could only support one or the other. Here we use intValue, so %d is supported but %f and %g are not.
The only thing you need to do is remove the autorelease.
You're malloc'ing and free'ing yourself - ARC doesn't care about that.
I write solution use NSInvocation and signatures.
Answer create in this.
Also I write detailed description how it work but only on Russian ((
Maybe it help for someone.
I tried mcfedr's code. Somehow, my Xcode 11 treated CVarArgType as undeclared type, so I investigated into this for a while.
I didn't not understand the closure part of his/her code. And, I just simplified to hard casted each element to CVarArg using as! operator.
func format(key: String, args: [Any]) -> String {
return String(format: key, arguments: args.map { ($0 as! CVarArg) })
}
let doubleValue: Double = 1.25
let floatValue: Float = 2.75
let intValue: Int = 3
let numberValue: NSNumber = 4.5 as NSNumber
let hello: String = "Hello"
let world: NSString = "World" as NSString
print(format(key: "double: %f, float: %f, int: %d, number: %#, %#, %#", args: [doubleValue, floatValue, intValue, numberValue, hello, world]))
// double: 1.250000, float: 2.750000, int: 3, number: 4.5, Hello, World
It seems it's working fine under swift 5.1, but there may be some pitfalls.

How to copy textField to OSX clipboard?

I'm stuck here. I know how to copy and paste on the iPhone side of things but how can I copy contents from a textField to the global clipboard in OSX. I've been searching the web but there are really no examples. So let me explain in detail what I'm trying to accomplish. I have a NSTextField named helloField and I want to be able to copy the contents of this helloField to the global pasteboard by pressing a button. How can this be done and is there certain libraries I need? Thanks.
On iOS
[UIPasteboard generalPasteboard].string = helloField.text;
On OSX
[[NSPasteboard generalPasteboard] clearContents];
[[NSPasteboard generalPasteboard] setString:helloField.stringValue forType:NSStringPboardType];
On macOS and Swift 3.x
let pasteBoard = NSPasteboard.general()
pasteBoard.clearContents()
pasteBoard.writeObjects([text as NSString])
For Swift 5
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.setString("string to copy", forType: .string)
Code to copy a string to the clipboard:
[[NSPasteboard generalPasteboard] clearContents];
[[NSPasteboard generalPasteboard] setString:copiedString forType:NSPasteboardTypeString];
NSStringPboardType is deprecated. There's a note in NSPasteboard.h about pboard types:
Use of pboard types should be replaced with use of UTIs. Pboard types will be deprecated in a future release.
Also in the header file:
APPKIT_EXTERN NSString *const NSPasteboardTypeString NS_AVAILABLE_MAC(10_6); // Replaces NSStringPboardType
...
APPKIT_EXTERN NSString *NSStringPboardType; //Use NSPasteboardTypeString
For Cocoa macOS in Swift 3:
let pasteBoard = NSPasteboard.general()
pasteBoard.clearContents()
pasteBoard.setString("something", forType: NSPasteboardTypeString)
You can create an extension for your String which supports iOS and macOS:
extension String {
func copy() {
#if os(macOS)
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.setString(self, forType: .string)
#else
UIPasteboard.general.string = self
#endif
}
}
Clipboard.set("some text")
class:
import AppKit
public class Clipboard {
public static func set(text: String?) {
if let text = text {
let pasteBoard = NSPasteboard.general
pasteBoard.clearContents()
pasteBoard.setString(text, forType: .string)
}
}
#available(macOS 10.13, *)
public static func set(url: URL?) {
guard let url = url else { return }
let pasteBoard = NSPasteboard.general
pasteBoard.clearContents()
pasteBoard.setData(url.dataRepresentation, forType: .URL)
}
#available(macOS 10.13, *)
public static func set(urlContent: URL?) {
guard let url = urlContent,
let nsImage = NSImage(contentsOf: url)
else { return }
let pasteBoard = NSPasteboard.general
pasteBoard.clearContents()
pasteBoard.writeObjects([nsImage])
}
public static func clear() {
NSPasteboard.general.clearContents()
}
}