NSTextView's jumps to the corresponding row via string - swift

I wanted to complete a novel reader, but it wasn't ideal to jump to the corresponding location based on the catalog title.
I use the following method to jump, but sometimes I don't get to the right place, and sometimes I get stuck or fail when the string length is too long。“mySubString(to:)” is my custom method
func scrollToPointByCatalog(string: String) {
textView.isEditable = true
let layout:NSLayoutManager = textView.layoutManager!
let container = textView.textContainer
let cutString = textView.textStorage?.string.mySubString(to: string)
let focusRingFrame:CGRect = layout.boundingRect(forGlyphRange: NSMakeRange(0, cutString!.count), in: container!)
scrollView.documentView!.scroll(NSPoint(x: 0, y:focusRingFrame.height))
textView.isEditable = false
}
Is there a good way to do that? It is a macOS software.

Related

Swift - Changing constant doesn't show error

I'm reading a tutorial from a book. I can't attach the book here. In a chapter an UIImage constant is declared and its value is assigned in next lines. It is not a var neither optional. It runs successfully. How does it work?
extension ViewController: MKMapViewDelegate {
private func addAnnotations() {
for business in businesses {
guard let yelpCoordinate = business.location.coordinate else {
continue
}
let coordinate = CLLocationCoordinate2D(latitude: yelpCoordinate.latitude,
longitude: yelpCoordinate.longitude)
let name = business.name
let rating = business.rating
let image:UIImage //Constant non-optional
switch rating {
case 0.0..<3.5:
image = UIImage(named: "bad")!
case 3.5..<4.0:
image = UIImage(named: "meh")!
case 4.0..<4.75:
image = UIImage(named: "good")!
case 4.75..<5.0:
image = UIImage(named: "great")!
default:
image = UIImage(named: "bad")!
}
let annotation = BusinessMapViewModel(coordinate: coordinate,
name: name,
rating: rating, image: image)
mapView.addAnnotation(annotation)
}
}
}
First, you should know that it is perfectly fine in Swift to declare a variable and assign it a value on the next line, as long as you don't refer to that variable before it is assigned.
let a: Int
... // you can't use "a" here
a = 10 // OK!
Look at the switch statement after the variable declaration. A switch statement must be exhaustive, meaning that at least one case of it will be run. In this switch statement, every case has a single statement that assigns to image, and there are no fallthroughs. From these observations, both us and the compiler can conclude that image will be assigned (and assigned only once) after the switch statement, hence you can use it in the line:
let annotation = BusinessMapViewModel(coordinate: coordinate,
name: name,
rating: rating, image: image)
From the Swift 5.1 reference, here
When a constant declaration occurs in the context of a function or method, it can be initialized later, as long as it is guaranteed to have a value set before the first time its value is read.

Can you send objects other than strings in URLQueryItems?

Ok, I am building an iMessage app and to transfer data back and forth I have to use URLQueryItems. I am working with an SKScene and need to transfer Ints, CGPoints, images, etc. Reading Apple's documentation and my own attempts it seems like you can only store strings in URLQueryItems.
As this us the only way to pass data back and forth, is there a (better) way to store other types of data? Currently I have been doing this:
func composeMessage(theScene: GameScene) {
let conversation = activeConversation
let session = conversation?.selectedMessage?.session ?? MSSession()
let layout = MSMessageTemplateLayout()
layout.caption = "Hello world!"
let message = MSMessage(session: session)
message.layout = layout
message.summaryText = "Sent Hello World message"
var components = URLComponents()
let queryItem = URLQueryItem(name: "score",value: theScene.score.description)
components.queryItems = [queryItem] //array of queryitems
message.url = components.url!
print("SENT:",message.url?.query)
conversation?.insert(message, completionHandler: nil)
}
Then on the flip side I have to convert this string back to an Int again. Doing this with CGPoints will be inefficient.. how would one pass something like a CGPoint in a URLQueryItem? Any other way than storing the x and y values as strings?
EDIT: This is how I have been receiving data from the other person and putting into their scene:
override func willBecomeActive(with conversation: MSConversation) {
// Called when the extension is about to move from the inactive to active state.
// This will happen when the extension is about to present UI.
// Use this method to configure the extension and restore previously stored state.
let val = conversation.selectedMessage?.url?.query?.description
print("GOT IT ", val)
if(val != nil)
{
scene.testTxt = val!
}
}
As you discovered, to pass data via URLQueryItem, you do have to convert everything to Strings since the information is supposed to be represented as a URL after all :) For CGPoint information, you can break the x and y values apart and send them as two separate Ints converted to String. Or, you can send it as a single String value in the form of "10,5" where 10 is the x and 5 is the y value but at the other end you would need to split the value on a comma first and then convert the resulting values back to Ints, something like this (at the other end):
let arr = cgPointValue.components(separatedBy:",")
let x = Int(arr[0])
let y = Int(arr[1])
For other types of data, you'd have to follow a similar tactic where you convert the values to String in some fashion. For images, if you have the image in your resources, you should be able to get away with passing just the name or an identifying number. For external images, a URL (or part of one if the images all come from the same server) should work. Otherwise, you might have to look at base64 encoding the image data or something if you use URLQueryItem but if you come to that point, you might want to look at what you are trying to achieve and if perhaps there is a better way to do it since large images could result in a lot of data being sent and I'm not sure if iMessage apps even support that. So you might want to look into limitations in the iMessage app data passing as well.
Hope this helps :)
You can use iMessageDataKit library for storing key-value pairs in your MSMessage objects. It makes setting and getting data really easy and straightforward like:
let message: MSMessage = MSMessage()
message.md.set(value: 7, forKey: "moveCount")
message.md.set(value: "john", forKey: "username")
message.md.set(values: [15.2, 70.1], forKey: "startPoint")
message.md.set(values: [20, 20], forKey: "boxSize")
if let moveCount = message.md.integer(forKey: "moveCount") {
print(moveCount)
}
if let username = message.md.string(forKey: "username") {
print(username)
}
if let startPoint = message.md.values(forKey: "startPoint") {
print("x: \(startPoint[0])")
print("y: \(startPoint[1])")
}
if let boxSize = message.md.values(forKey: "boxSize") {
let size = CGSize(width: CGFloat(boxSize[0] as? Float ?? 0),
height: CGFloat(boxSize[1] as? Float ?? 0))
print("box size: \(size)")
}
(Disclaimer: I'm the author of iMessageDataKit)

Swift - How to get all occurrences of a range?

I'm trying to use regex and range for the first time in Swift. I want to see if the letter the user enters into the textfield will match the word that they have to guess. If it does match the matching letter or letters will be displayed in a UILabel (similar to how you play hangman, if you guess the correct letter once and there are multiple occurrences of that letter, all occurrences will show). When a button is clicked the method below is called. It works fine when finding the matching letters, and inserting them at the right location, BUT when the UILabel is updated after the loop it only updates the label with the result of the second/final loop. How can I get a combination of the result from all the iterations of the loop? Any help would be appreciated. Thank you
func findLetter(displayedWord toSearchin: String, userInput toSearchFor: String) {
let ranges: [NSRange]
var labelUpdate = String()
do {
let regex = try NSRegularExpression(pattern: toSearchFor, options: [])
let displayedWord = toSearchin as NSString
let rangeOfSearch = NSMakeRange(0, displayedWord.length)
ranges = regex.matches(in: toSearchin, range: rangeOfSearch).map {$0.range}
let nsStringlabel = wordLabel.text as NSString?
for range in ranges {
labelUpdate = (nsStringlabel?.replacingCharacters(in: range, with: toSearchFor))!
print(labelUpdate)
//the word is lavenders, so this prints:
//___e_____
//______e__
// I want:
//___e__e__
}
DispatchQueue.main.async(execute: {
self.wordLabel.text = labelUpdate
})
}
catch {
ranges = []
}
}
As stated in a comment, you’re always updating the original nsStringlabel variable, thus always overriding the previous modification in the previous loop run.
I’d recommend you init labelUpdate with wordLabel.text as NSString? and completely remove nsStringlabel. This should solve your problem.
That being said, there are a lot of other problems that could be fixed in this function.
In particular, why use regexes? It’s expensive and not useful there.
Also, you’re dispatching before setting the label value, which is good, but not before retrieving the value… either a dispatch is needed or it is not, but it cannot be needed at one place and not at the other. If you call your function from the main thread (as a response to a user input for instance), you should be good and not need a dispatch.
Here what I would have done (should be safer and faster):
func updateLabel(withDestinationWord destinationWord: String, userInput: String) {
var labelText = wordLabel.text
var startIndex = labelText.characters.startIndex
while let r = destinationWord.range(of: userInput, options: .caseInsensitive, range: startIndex..<labelText.characters.endIndex) {
labelText.replaceSubrange(r, with: userInput)
startIndex = labelText.characters.index(after: r.lowerBound)
}
wordLabel.text = labelText
}
Be sure to have the same length for wordLabel.text and destinationWord!

Use Measurement for input in NSTextFieldCell

I want to be able to nicely use a Measurement and MeasurementFormatter for output and input with a NSTextFieldCell.
I am able to display the measurement correctly with...
let areaFormatter = MeasurementFormatter()
areaFormatter.unitStyle = .medium
areaFormatter.unitOptions = .providedUnit
let area = Measurement<UnitArea>( value: 123.43, unit: .squareInches)
let editInput = NSTextFieldCell
editInput.objectValue = area
editInput.formatter = areaFormatter
This displays something like
123.43 in^2
The problem starts when I want to read this back in with
var inputArea = editInput.objectValue as! Measurement<UnitArea>
I think because the get Object value of the Measurement Formatter is not defined.
open func getObjectValue(_ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?, for string: String, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool
Is my understanding correct? Are there any examples in Swift where this function has been defined for measurements?
Right now the user can edit the entire string including the text in the units. Is there a good way to block the units in the NSTextFieldCell? I would like the user to be able to edit the number but not the units and then return the measurement with
var inputArea = editInput.objectValue as! Measurement<UnitArea>
so this gets displayed
123.43 in^2
but only the 123.43 can be edited.

How do I 'seed' using GKRandomSource from Swift's Gameplaykit to remember the shuffle between sessions

I'm new to programming and have been learning Swift by doing a number of online courses. In one of the courses we built a basic trivia game and I've been progressively trying to improve it by doing my own coding (best way to learn!).
Recently I came across what's called a Fisher-Yates shuffle and, after much trial and error (and with the help of the stack overflow community) was able to use GKRandomSource from Swift's Gameplaykit to shuffle my trivia questions around so that they were being asked randomly. This was an improvement on the original arc4random code I was using because the shuffle removed questions already asked from the overall pool of questions, thereby ensuring that they did not repeat (at least in iOS9).
This works well within the session, but once the user quits the app and relaunches it, the shuffle starts from scratch. So I was looking at a way to have the app 'remember' the questions already asked between sessions. My research led me to the idea of seeding and I've been trying to get this to work with my GKRandomSource code, but I'm obviously missing something.
Any advice etc would be most welcome - especially since I'm not entirely sure that this 'seeding' approach will achieve my ultimate aim of not repeating questions already asked in previous sessions of the app.
Below are what I believe to be the relevant bits of my revised code.
All questions and potential answer choices are stored in a .json file as such:
{
"id" : "1",
"question": "Earth is a:",
"answers": [
"Planet",
"Meteor",
"Star",
"Asteroid"
],
"difficulty": "1"
}
I use the following code to load the .json file:
func loadAllQuestionsAndAnswers()
{
let path = NSBundle.mainBundle().pathForResource("content", ofType: "json")
let jsonData : NSData = NSData(contentsOfFile: path!)!
allEntries = (try! NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers)) as! NSArray
//println(allEntries)
}
And below is my most recent code for trying to achieve the shuffle of all questions and replicate it in future sessions):
var allEntries : NSArray!
var shuffledQuestions: [AnyObject]!
var nextQuestion = -1
var mySeededQuestions : [AnyObject]
loadAllQuestionsAndAnswers()
if #available(iOS 9.0, *) {
let lcg = GKLinearCongruentialRandomSource(seed: mySeededQuestions)
let shuffledQuestions = lcg.arrayByShufflingObjectsInArray(allEntries)
nextQuestion++
loadQuestion(nextQuestion)
// Fallback on earlier versions
}else{
let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
loadQuestionPreiOS9(randomNumber)
}
I know at the very least I have a problem with the above code, but I'm at a loss. I'm also thinking that maybe I'm missing a step in terms of storing the seed?
For the sake of completeness, I use a label to display the question and four images to display the potential answers, using the following code:
func loadQuestion(index : Int)
{
let entry : NSDictionary = shuffledQuestions[index] as! NSDictionary
let question : NSString = entry.objectForKey("question") as! NSString
let arr : NSMutableArray = entry.objectForKey("answers") as! NSMutableArray
//println(question)
//println(arr)
labelQuestion.text = question as String
let indices : [Int] = [0,1,2,3]
//let newSequence = shuffle(indices)
let newSequence = indices.shuffle()
var i : Int = 0
for(i = 0; i < newSequence.count; i++)
{
let index = newSequence[i]
if(index == 0)
{
// we need to store the correct answer index
currentCorrectAnswerIndex = i
}
let answer = arr.objectAtIndex(index) as! NSString
switch(i)
{
case 0:
buttonA.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 1:
buttonB.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 2:
buttonC.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 3:
buttonD.setTitle(answer as String, forState: UIControlState.Normal)
break;
default:
break;
}
}
buttonNext.hidden = true
// we will need to reset the buttons to reenable them
ResetAnswerButtons()
}
func loadQuestionPreiOS9(index : Int)
{
let entry : NSDictionary = allEntries.objectAtIndex(index) as! NSDictionary
let question : NSString = entry.objectForKey("question") as! NSString
let arr : NSMutableArray = entry.objectForKey("answers") as! NSMutableArray
//println(question)
//println(arr)
labelQuestion.text = question as String
let indices : [Int] = [0,1,2,3]
//let newSequence = shuffle(indices)
let newSequence = indices.shuffle()
var i : Int = 0
for(i = 0; i < newSequence.count; i++)
{
let index = newSequence[i]
if(index == 0)
{
// we need to store the correct answer index
currentCorrectAnswerIndex = i
}
let answer = arr.objectAtIndex(index) as! NSString
switch(i)
{
case 0:
buttonA.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 1:
buttonB.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 2:
buttonC.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 3:
buttonD.setTitle(answer as String, forState: UIControlState.Normal)
break;
default:
break;
}
}
buttonNext.hidden = true
// we will need to reset the buttons to reenable them
ResetAnswerButtons()
}
Finally, I use the following code to present the user with a 'Next' button after they've answered a question:
#IBAction func PressedButtonNext(sender: UIButton) {
print("button Next pressed")
if #available(iOS 9.0, *) {
nextQuestion++
loadQuestion(nextQuestion)
}else{
let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
loadQuestionPreiOS9(randomNumber)
}
I know my coding is probably quite verbose and unnecessary, but up until this latest improvement it's been working fine and I actually understand most of it (I think!)
There are really two questions here: what you're asking about and what you seem to want. They're both worth answering for different reasons, so...
How to seed a GK(Whatever)RandomSource
(All the GKRandomSource subclasses have seeds, even though the superclass GKRandomSource itself doesn't... that's because each class has its own data type for seeds. But the usage is the same.)
The critical bits of the code you've posted don't even compile due to a type mismatch: the seed/init(seed:) value for GKLinearCongruentialRandomSource is an integer, not an array of objects. The documentation for that value spells out what it's for (emphasis added) and how to use it:
Any two random sources initialized with the same seed data will generate the same sequence of random numbers. To replicate the behavior of an existing GKLinearCongruentialRandomSource instance, read that instance’s seed property and then create a new instance by passing the resulting data to the initWithSeed: initializer.
So, if you want to replicate a sequence of random numbers:
Create a random source with the plain initializer.
let source = GKLinearCongruentialRandomSource()
Save off that source's seed value.
let seed = source.seed // -> some UInt64 value
// write seed to user defaults, a file, a web service, whatever.
Use that random source for whatever.
Later, when you launch again and want the same sequence, read in the seed value and create a random source using the seed.
let seed = // read in seed value from wherever you saved it
let source = GKLinearCongruentialRandomSource(seed: seed)
This still doesn't get you what you're actually looking for, though: If source in step 1 produced the sequence 1, 6, 3, 9, 2, 7, source from step 4 will also produce the sequence 1, 6, 3, 9, 2, 7 — the seed doesn't record where you "left off" in a sequence. Or, since you're using it for an array shuffle, it'll produce the same shuffled ordering of the array as the first shuffle, but it doesn't remember what you did with the shuffled array thereafter.
How to use a shuffled ordering across multiple app launches
If you want to shuffle an array, walk through it in order, and then on a later run of your app continue walking through the same shuffled array from where you left off, you need to design around that requirement.
Shuffle on the first launch.
Record something about the ordering produced. (Say, a mapping of indices in the shuffle to indices in the original data.)
When walking through the shuffled array, record how far you've gone through it.
On later runs of the app, use the record of the ordering and the record of progress to decide where you are.
Here's a rough pass at that. (Note that I'm not touching your data model — this is a program design question, and SO is not a coding service. You'll need to think about how to flesh out this design to match your model and its use cases.)
struct Defaults {
static let lastQuestionIndex = "lastQuestionIndex"
static let questionOrder = "questionOrder"
}
let questions: [Question] // array of model objects, always in fixed order
func nextQuestion() -> Question {
let defaults = NSUserDefaults.standardUserDefaults()
if let lastIndex = defaults.integerForKey(Defaults.lastQuestionIndex) {
// we've run before, load the ordering
guard let shuffledOrder = defaults.arrayForKey(Defaults.questionOrder) as? [Int]
else { fatalError("save questionOrder with lastQuestionIndex") }
// advance the saved index so the next call to this function
// will get the next question
if lastIndex + 1 < count {
defaults.setInteger(lastIndex + 1, forKey: Defaults.lastQuestionIndex)
} else {
// ran out of shuffled questions, forget the order so we
// can reshuffle on the next call
defaults.removeObjectForKey(Defaults.questionOrder)
defaults.removeObjectForKey(Defaults.lastQuestionIndex)
}
// map lastQuestionIndex from sequential to shuffled
// and return the corresponding answer
let shuffledIndex = shuffledOrder[lastIndex]
return questions[shuffledIndex]
} else {
// first run, shuffle the question ordering (not the actual questions)
let source = GKRandomSource()
let sequentialOrder = Array(0..<questions.count)
let shuffledOrder = source.arrayByShufflingObjectsInArray(sequentialOrder)
// save the ordering, and the fact that we're asking the first question
defaults.setObject(shuffledOrder, forKey: Defaults.questionOrder)
defaults.setInteger(0, forKey: Defaults.lastQuestionIndex)
// return the first question in the shuffled ordering
let shuffledIndex = shuffledOrder[0]
return questions[shuffledIndex]
}
}
That's probably a bit pseudocode-ish (so you might need to worry about casting arrays to work with NSUserDefaults, etc), but as a general design it should be enough to give you some food for thought.
You can also use the following to drop off a certain amount of values so if you keep a roll count dropping that many on next start is as easy as :
arc4.dropValues(rollCount)