Append to NSTextView and scroll - objective-c

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))
}
}

Related

again convert textKit from Objective-C to Swift

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]
}

Translating Objective-C to Swift - Error: Type 'Int' does not conform to protocol 'BooleanType'

I searched on google and on SO but didn't find any useful help for this issue.
I'm trying to translate this code from objective-c to swift:
- (void)metaTitleUpdated:(NSString *)title {
NSLog(#"delegate title updated to %#", title);
NSArray *chunks = [title componentsSeparatedByString:#";"];
if ([chunks count]) {
NSArray *streamTitle = [[chunks objectAtIndex:0] componentsSeparatedByString:#"="];
if ([streamTitle count] > 1) {
titleLabel.text = [streamTitle objectAtIndex:1];
}
}
}
so far i have translated it to this:
func metaTitleUpdated(input: String) {
println("delegate title updated to \(title)")
let chunks: NSArray = title!.componentsSeparatedByString(";")
if (chunks.count) {
let streamTitle = chunks .objectAtIndex(0) .componentsSeparatedByString(";")
if (streamTitle.count > 1) {
titleLabel.text = streamTitle.objectAtIndex(1)
}
}
}
but i always get the error "Type 'Int' does not conform to protocol 'BooleanType'" in the line: if (chunks.count) {
What does cause this error? Is the rest of the code in swift correct or are there any other errors?
chunks.count has the type Int, but the if statement requires a boolean expression.
This is different from (Objective-)C, where the controlling expression of an if statement can have any scalar type and is compared with zero.
So the corresponding Swift code for
if ([chunks count]) { ... }
is
if chunks.count != 0 { ... }
I solved the answer by myself.
func metaTitleUpdated(title: String) {
var StreamTitle = split(title) {$0 == "="}
var derRichtigeTitel: String = StreamTitle[1]
titleLabel.text = derRichtigeTitel
println("delegate title updated to \(derRichtigeTitel)")
}

Objective C - Change System Font with Category

I want to change the default font for all UITextViews. It seems that the easiest way to do this is via custom category. I found this solution: Change the default systemFont used by controls and tried to implement it.
But my UITextViews are added programmatically so the awakeFromNib function is not called. I tried to move it to initWithFrame like this:
-(id)initWithFrame:(CGRect)frame
{
id result = [super initWithFrame:frame];
if (result) {
float size = [self.font pointSize];
NSString *stringfontstyle=self.font.fontName;
if([stringfontstyle rangeOfString:#"Bold"].location != NSNotFound) {
self.font = [UIFont fontWithName:#"Avenir-Black" size:size];
}
else if ([stringfontstyle rangeOfString:#"Italic"].location != NSNotFound) {
self.font = [UIFont fontWithName:#"Avenir-Oblique" size:size];
}
else if ([stringfontstyle rangeOfString:#"Medium"].location != NSNotFound) {
self.font = [UIFont fontWithName:#"Avenir-Medium" size:size];
}
else {
self.font = [UIFont fontWithName:#"Avenir-Roman" size:size];
}
}
return result;
}
Weird is that if my category contains initWithFrame function, the UITextView disappears. What is it that I'm missing?
Note: I'm using autoLayout so the initWithFrame is called with CGRectZero, but I suppose that isn't the problem.
EDIT:
The problem is that the font is null when the UITextView is initiated. So what method would be appropriate to place the code into?
when category contains a method, it overrides the class's method... and thats not good. subclassing would work.. method swizzling might be a way but...
why don't you just subclass UITextView - then you can keep your initWithFrame thingy or maybe override font
- (UIFont*)font {
if(!myFont) {
_myFont = xy;
}
id superFont = super.font;
if(![superFont.name isEqualTo:_myFont.name]) {
super.font = [myFont fontWithSize:superFont.pointSize];
}
return _myFont;
}
or setFont:
- (void)setFont:(UIFont*)newFont {
if(!myFont) {
_myFont = xy;
}
id thisFont = [_myFont fontWithSize:newFont.pointSize];
super.font = thisFont;

UITEXTVIEW: Get the recent word typed in uitextview

I want to get the most recent word entered by the user from the UITextView.
The user can enter a word anywhere in the UITextView, in the middle or in the end or in the beginning. I would consider it a word when the user finishes typing it and presses a space and does any corrections using the "Suggestions from the UIMenuController".
Example: User types in "kimd" in the UITextView somewhere in the middle of text, he gets a popup for autocorrection "kind" which he does. After he does that, I want to capture "kind" and use it in my application.
I searched a lot on the internet but found solutions that talk about when the user enters text in the end. I also tried detecting a space and then doing a backward search until another space after some text is found, so that i can qualify it as a word. But I think there may be better ways to do this.
I have read somewhere that iOS caches the recent text that we enter in a text field or text view. If I can pop off the top one , that's all I want. I just need handle to that object.
I would really appreciate the help.
Note: The user can enter text anywhere in UItextview. I need the most recent entered word
Thanks.
//This method looks for the recent string entered by user and then takes appropriate action.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
//Look for Space or any specific string such as a space
if ([text isEqualToString:#" "]) {
NSMutableCharacterSet *workingSet = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
NSRange newRange = [self.myTextView.text rangeOfCharacterFromSet:workingSet
options:NSBackwardsSearch
range:NSMakeRange(0, (currentLocation - 1))];
//The below code could be done in a better way...
UITextPosition *beginning = myTextView.beginningOfDocument;
UITextPosition *start = [myTextView positionFromPosition:beginning offset:currentLocation];
UITextPosition *end = [myTextView positionFromPosition:beginning offset:newRangeLocation+1];
UITextRange *textRange = [myTextView textRangeFromPosition:end toPosition:start];
NSString* str = [self.myTextView textInRange:textRange];
}
}
Here is what I would suggest doing, might seem a little hacky but it would work just fine:
First in .h conform to the UITextViewDelegate and set your text view's delegate to self like this:
myTextView.delegate = self;
and use this code:
- (void)textViewDidChange:(UITextView *)textView { // Delegate method called when any text is modified
if ([textView.text substringFromIndex: [textView.text length] - 1]) { // Gets last character of the text view's text
NSArray *allWords = [[textView text] componentsSeparatedByString: #" "]; // Gets the text view's text and fills an array with all strings seperated by a space in text view's text, basically all the words
NSString *mostRecentWord = [allWords lastObject]; // The most recent word!
}
}
I use this code to get the word behind the #-sign:
- (void)textViewDidChange:(UITextView *)textView {
NSRange rangeOfLastInsertedCharacter = textView.selectedRange;
rangeOfLastInsertedCharacter.location = MAX(rangeOfLastInsertedCharacter.location - 1,0);
rangeOfLastInsertedCharacter.length = 1;
NSString *lastInsertedSubstring;
NSString *mentionSubString;
if (![textView.text isEqualToString:#""]) {
lastInsertedSubstring = [textView.text substringWithRange:rangeOfLastInsertedCharacter];
if (self.startOfMention > 0 || self.startOfHashtag > 0) {
if ([lastInsertedSubstring isEqualToString:#" "] || (self.startOfMention > textView.selectedRange.location || self.startOfHashtag > textView.selectedRange.location)) {
self.startOfMention = 0;
self.lenthOfMentionSubstring = 0;
}
}
if (self.startOfMention > 0) {
self.lenthOfMentionSubstring = textView.selectedRange.location - self.startOfMention;
NSRange rangeOfMentionSubstring = {self.startOfMention, textView.selectedRange.location - self.startOfMention};
mentionSubString = [textView.text substringWithRange:rangeOfMentionSubstring];
dhDebug(#"mentionSubString: %#", mentionSubString);
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
}
}
}
Simple extension for UITextView:
extension UITextView {
func editedWord() -> String {
let cursorPosition = selectedRange.location
let separationCharacters = NSCharacterSet(charactersInString: " ")
let beginRange = Range(start: text.startIndex.advancedBy(0), end: text.startIndex.advancedBy(cursorPosition))
let endRange = Range(start: text.startIndex.advancedBy(cursorPosition), end: text.startIndex.advancedBy(text.characters.count))
let beginPhrase = text.substringWithRange(beginRange)
let endPhrase = text.substringWithRange(endRange)
let beginWords = beginPhrase.componentsSeparatedByCharactersInSet(separationCharacters)
let endWords = endPhrase.componentsSeparatedByCharactersInSet(separationCharacters)
return beginWords.last! + endWords.first!
}
}

Get selection (highlighted text) string from NSTextView Objective-C

How can I get the selected text's string from a NSTextView as an NSString?
Your help is greatly appreciated.
An NSText can have more than only one selection. Check it out with TextEditapp: select a string with the mouse while pressing CMD. So you can select as many strings as you want. Therefore I think, a more common solution is to use:
NSArray *ranges = [myTextView selectedRanges];
and then extract the strings one by one.
Since NSTextView is a subclass of NSText, you can use NSText instance methods to figure out the selected string like so:
NSString *selected = [[myTextView string]
substringWithRange:[myTextView selectedRange]];
Swift 5, handling multiple selections of NSTextView
based on #vauxhall's answer
extension NSTextView {
var selectedText: String {
var text = ""
for case let range as NSRange in self.selectedRanges {
text.append(string[range]+"\n")
}
text = String(text.dropLast())
return text
}
}
extension String {
subscript (_ range: NSRange) -> Self {
.init(self[index(startIndex, offsetBy: range.lowerBound) ..< index(startIndex, offsetBy: range.upperBound)])
}
}
Swift
extension NSTextView {
var selectedText: String {
string[selectedRange()]
}
}
extension String {
subscript (_ range: NSRange) -> Self {
.init(self[index(startIndex, offsetBy: range.lowerBound) ..< index(startIndex, offsetBy: range.upperBound)])
}
}
Usage
let textView = NSTextView()
print(textView.selectedText)