NSRange search in UITextView with NSMutableAttributedString slow - ios6

I am trying to search through the contents of my attributed UITextView with the following code:
NSRange range = NSMakeRange(0, haystack.length);
range = [haystack rangeOfString:searchText options:NSCaseInsensitiveSearch range:range];
while (range.location != NSNotFound)
{
[_attrString addAttribute:NSBackgroundColorAttributeName value:[UIColor yellowColor] range:NSMakeRange(range.location, range.length)];
range = NSMakeRange(range.location+range.length, haystack.length-(range.location+range.length));
range = [haystack rangeOfString:searchText options:NSCaseInsensitiveSearch range:range locale:nil];
}
...
_textView.attributedText = _attrString;
_attrString is of course a NSMutableAttributedString
This works fine except it is very slow with large texts. With a UITextView containing 156,000 characters it takes a couple of seconds for the changes to become visible. If I NSLog the single steps of the loop I can see that the code executes fast. It takes a couple of seconds for the changes to become visible in the UITextView.
Does it just take a while for the attributed UITextview to redraw? Is do anything to speed up the process? I tried searching through the text with regular expressions, but that didn't seem to change anything.
Thanks

Use NSPredicate
UITextField *txtFld=(UITextField *)sender;
// if (![txtFld.text isEqualToString:#""]) {
//[self getSearchResult:txtFld.text];
// }else{
// [self makeViewSmall];
// }
// For string kind of values:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains[cd] %#", txtFld.text];
NSArray *results = [[sortedDic valueForKey:#"locations"] filteredArrayUsingPredicate:predicate];
NSLog(#"Result=%#",results);*/

Related

SwiftUI Large Array (over 30K elements) taking forever to iterate over

I'm so confused as to what's going on but basically, this is my code. Maybe I just am stupid or do not enough above swift or something but I feel like this should take less than a second, but it takes a very long time to iterate through (i added the CFAbsoluteTimeGetCurrent) because I wanted to see how long each assignment took and they take around 0.0008949041366577148 seconds each, but over the span of 30K that adds up obviously. This is just test code but what I'm really trying to do is implement Agglomerative Clustering using a single linkage in Swift, at first I thought that my algorithm was written poorly but then I just tried to iterate over an array like this and it still took a long time. Does anyone know what's up?
I'm also aware that printing out statements in the console takes time but even after removing these statements the onAppear closure still took a while to finish.
Also sorry this is my first time ever posting on Stack Overflow so if please let me know if I should write my posts a certain way in the future.
#State private var mat: [Double?] = Array(repeating: nil, count: 30000)
var body: some View {
Text("HELLo")
.onAppear() {
for i in 0..<matrix.count {
let start = CFAbsoluteTimeGetCurrent()
mat[i] = 0
let diff = CFAbsoluteTimeGetCurrent() - start
print("HAC_SINGLELINK.init TIME: \(diff), ROW \(i) of \(matrix.count)")
}
}
}
I believe the time is caused by the number of modifications you do to your #State variable, which cause a lot of overhead.
With your initial code, on my machine, it took ~16 seconds. With my modified code, which does all of the modifications on a temporary non-state variable and then assigns to #State once, it takes 0.004 seconds:
.onAppear() {
let start = CFAbsoluteTimeGetCurrent()
var temp = matrix
for i in 0..<temp.count {
temp[i] = 0
}
let diff = CFAbsoluteTimeGetCurrent() - start
matrix = temp
print("HAC_SINGLELINK.init TIME: \(diff) \(matrix.count)")
}

Splitting string into array string.components(separtedBy: ",") consumes more time

I have text file which contains 18000 lines which have cities names. Each line has city name, state, latitude, longitude etc. Below is the function which does that, if i don't implement string.components(separtedBy: ", ") loading function is pretty fast but with it implemented it takes time which makes my UI freeze. What is the right way of doing it? Is string.components(separtedBy: ", ") that costly?
I profiled the app, this line is taking string.components(separtedBy: ", ") 1.45s out of 2.09s in whole function.
func readCitiesFromCountry(country: String) -> [String] {
var cityArray: [String] = []
var flag = true
var returnedCitiesList: [String] = []
if let path = Bundle.main.path(forResource: country, ofType: "txt") {
guard let streamReader = StreamReader(path: path) else {fatalError()}
defer {
streamReader.close()
}
while flag {
if let nextLine = streamReader.nextLine() {
cityArray = nextLine.components(separatedBy: ",") // this is the line taking a lot of time, without this function runs pretty fast
if (country == "USA") {
returnedCitiesList.append("\(cityArray[0]) , \(cityArray[1]) , \(cityArray[2])")
} else {
returnedCitiesList.append("\(cityArray[0]) , \(cityArray[1])")
}
//returnedCitiesList.append(nextLine)
} else {
flag = false
}
}
} else {
fatalError()
}
return returnedCitiesList
}
StreamReader used in the code can be found here. It helps to read file line by line
Read a file/URL line-by-line in Swift
This question is not about how to split the string into array Split a String into an array in Swift? , rather why splitting is taking more time in the given function.
NSString.components(separatedBy:) returns a [String], which requires that all of the pieces' content be copied, from the original string, and pasted into new-ly allocated stringss. This slows things down.
You could address the symptoms (UI freezing) by putting this work on a background thread, but that just sweeps the problem under the wrong (the inefficient copying is still there), and complicates things (async code is never fun).
Instead, you should consider using String.split(separator:maxSplits:omittingEmptySubsequences:), which returns [Substring]. Each Substring is just a view into the original string's memory, which stores the relevant range so that you only see that portion of the String which is modeled by the Substring. The only memory allocation happening here is for the array.
Hopefully that should be enough to speed your code up to acceptable levels. If not, you should combine both solutions, and use split off-thread.

Swift, Generating Array Variables of a Numbered List

I'm making a simple game in swift and xcode and I ran into this problem that I can't figure out. Because I have so many levels, the code locks up indexing and slows down the whole program. I get a color wheel spinning for a few minutes but it never crashes. Just takes several minutes everytime I type in a few characters. Strange, but xcode has always had it's bugs right?
Each Button ("button1, button2..." below) gets a single number from "level1:Array". It's like a code that fills in the button's value for the game. There are only 4 buttons, but the numbers should be able to change as they each have their own variables.
I want to generate this for every level. I should be able to generate something like "button1 = level#[0]" where # is replaced by "userLevel". Changing to a string and doing something like "button1 = ("level(userLevel)") as! Array... doesn't seem to work. Look below and use my terminology when giving examples if you can. Thanks!
Here's the example:
let level1:Array = [9,7,4,1] // current puzzle, to go directly into button vars below (button1,button2,ect)
var userLevel = 1 // current user's level
if userLevel == 1 {
print("making Level 1, setting buttons to value from array")
button1 = level1[0]
button2 = level1[1]
button3 = level1[2]
button4 = level1[3]
}
Now, since level# is repeated so often (for each level as the number progresses) I would rather just make it something like this:
//this doesn't work in swift, but what else can I do?
if userLevel > 0 {
button1 = level\(userLevel)[0]
button2 = level\(userLevel)[1]
button3 = level\(userLevel)[2]
button4 = level\(userLevel)[3]
}
Is there an easy way to do this? Thanks!
-GG
Try using a for-in loop. Create an array of the buttons, and then
var index = 0
for button in buttons {
button = level1[index]
index++
}
EDIT since you want both the user level and the level number to increase, I suggest you define the levels like this. (Make sure that the number of buttons is equal to the number of userLevels, otherwise you will have problems)
var array = [1,2,3]
let levels = [1:[1,3,8],2:[3,6,4],3:[4,2,5]]
var index = 0
if array.count == levels.count {
for number in array {
array[index] = levels[index+1]![index]//The second index can be 0 if you want
index++
}
}
//array = [1,6,5]
// You could create a second index to match the number of levels within the main user level.
In this case, assume array to be your array of buttons
EDIT 2 :)
I've made a function that will allow you to assign all the levels to your array for a specific userLevel, since I see that is what you want
let levels = [1:[1,3,8],2:[3,6,4],3:[4,2,5]]
func assignValuesToArray(levelNo:Int) -> [Int] {
var array: [Int] = []
if (levelNo > 0) && (levelNo <= levels.count) {
for (level,levelArray) in levels {
if level == levelNo {
for value in levelArray {
array.append(value)
}
}
}
return array
} else {
print("This Level number does not exist")
return []
}
}
var finalArray = assignValuesToArray(2)
print(finalArray) // Returns [3,6,4]
As you can see from this example, you will return your array of buttons from the function, and you can assign the returned array values to whatever you like.

Picking a random sprite node from an array in Sprite Kit

I am trying to create an app in which one of a few objects is randomly placed on the screen wherever you touch. I have my objects a, b, and c and I have them in an array.
(NSArray *) gamePieces {
NSArray *things = [NSArray arrayWithObjects: #"a", #"b", #"c", nil];
return things;
}
And then my touch method.
(void)touchesBegan:(NSArray *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
// code that chooses random object from array
}
}
Normally I would put in an SKSpriteNode and it would make just that one appear on the screen, but I am looking to randomly select one of many. I'm new to programming and I'm not sure if I'm on the right path. If I am, what needs to go in the touch method? If not, what am I doing wrong?
You can get a random item by getting a random index based on the number of elements in the array :
int randomIndex = arc4Random() % things.count;
NSString *thing = things[randomIndex];
// do something with thing

raw sound byteArray to float Array

I'm trying to convert the byteArray of a Sound Object to an array with floats. The Sound Object plays back fine & at full length, but the float Array i get from it is cut off (but sounds correct), so i must be doing something wrong in the conversion:
var s:Sound = mySound;
s.play(); // plays fine
var bytes:ByteArray = new ByteArray();
bytes.endian = Endian.LITTLE_ENDIAN;
s.extract(bytes, s.bytesTotal, 0);
var leftChannel:Array = new Array();
var rightChannel:Array = new Array();
bytes.position = 0;
while (bytes.bytesAvailable)
{
leftChannel.push(bytes.readFloat());
rightChannel.push(bytes.readFloat());
}
and this is what i get:
The top two channels are the original Sound Object.
The lower two is the float Array Data. I aligned them so you can see that the beginning is cut off and obviously the length is incorrect.
Thanks for any answers...
ok there were two problems:
the mp3 file i was importing was somehow corrupt, that caused the beginning to be cut off
the length i defined to extract was not correct, to find the full sound length use
var numTotalSamples:Number = int(s.length * 44.1); //assuming 44.1kHz sample rate
then:
s.extract(bytes, numTotalSamples, 0);

Resources