Use Measurement for input in NSTextFieldCell - swift

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.

Related

NSTextView's jumps to the corresponding row via string

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.

Trying to concatenate a string with what I think is an unicode suffix

I have this app of mine that reads datamatrix barcodes from drugs using the camera.
When it does for a particular drug, I receive this string from the detector, as seen on Xcode console:
0100000000D272671721123110700XXXX\U0000001d91D1
my problem is that \U0000001d91D1 part.
This code can be decomposed on the following:
01 00000000D27267 17 211231 10 700XXXX \U0000001d 91D1"
01 = drug code
17 = expiring date DMY
10 = batch number
The last part is the dosage rate
Now on another part of the application I am on the simulator, with no camera, so I need to pass this string to the module that decomposes the code.
I have tried to store the code as a string using
let code = "0100000000D272671721123110700XXXX\U0000001d91D1"
it complains about the inverted bar, so I change it to double bar
let code = "0100000000D272671721123110700XXXX\\U0000001d91D1"
the detector analyzes this string and concludes that the batch number is 700XXXX\U0000001d91D1, instead of just 700XXXX, so the information contained from the \ forward is lost.
I think this is unicode or something.
How do I create this string correctly.
You can use string transform to decode your hex unicode characters:
let str1 = #"0100000000D272671721123110700XXXX\U00000DF491D1"#
let str2 = #"0100000000D272671721123110700XXXX\U0000001d91D1"#
let decoded1 = str1.applyingTransform(.init("Hex-Any"), reverse: false)! // "0100000000D272671721123110700XXXX෴91D1"
let decoded2 = str2.applyingTransform(.init("Hex-Any"), reverse: false)! // "0100000000D272671721123110700XXXX91D1"
You can also get rid of the verbosity extending StringTransform and StringProtocol:
extension StringTransform {
static let hexToAny: Self = .init("Hex-Any")
static let anyToHex: Self = .init("Any-Hex")
}
extension StringProtocol {
var decodingHex: String {
applyingTransform(.hexToAny, reverse: false)!
}
var encodingHex: String {
applyingTransform(.anyToHex, reverse: false)!
}
}
Usage:
let str1 = #"0100000000D272671721123110700XXXX\U00000DF491D1"#
let str2 = #"0100000000D272671721123110700XXXX\U0000001d91D1"#
let decoded1 = str1.decodingHex // "0100000000D272671721123110700XXXX෴91D1"
let decoded2 = str2.decodingHex // "0100000000D272671721123110700XXXX91D1"
The \U0000001d substring probably represents code point U+001D INFORMATION SEPARATOR THREE, which is also the ASCII code point GS (group separator).
In a Swift string literal, we can write that code point using a Unicode escape sequence: \u{1d}. Try writing your string literal like this:
let code = "0100000000D272671721123110700XXXX\u{1d}91D1"

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)

Using three arrays to populate complication timeline

I have three arrays that have the data to populate the complication timeline with entries.
When I scroll through time travel, the complication does not change so I know I must be doing something wrong.
func getTimelineEntriesForComplication(complication: CLKComplication, afterDate date: NSDate, limit: Int, withHandler handler: (([CLKComplicationTimelineEntry]?) -> Void)) {
for headerObject in headerArray! {
for body1Object in body1Array! {
for body2Object in body2Array! {
let headerTextProvider = CLKSimpleTextProvider(text: headerObject as! String)
let body1TextProvider = CLKSimpleTextProvider(text: body1Object as! String)
let body2TextProvider = CLKRelativeDateTextProvider(date: body2Object as! NSDate, style: .Offset, units: .Day)
print("HeaderTextProvider: \(headerTextProvider)")
print("Body1TextProvider: \(body1TextProvider)")
print("Body2TextProvider: \(body2TextProvider)")
let template = CLKComplicationTemplateModularLargeStandardBody()
template.headerTextProvider = headerTextProvider
template.body1TextProvider = body1TextProvider
template.body2TextProvider = body2TextProvider
let timelineEntry = CLKComplicationTimelineEntry(date: body2Object as! NSDate, complicationTemplate: template)
entries.append(timelineEntry)
print("TimeEnt: \(entries)")
print("TimeEntCount: \(entries.count)")
}
}
}
handler(entries)
}
My thinking:
Loop through the three arrays
Set the template with the results of the array loops
Set the timeline entry with the date of the object in body2Array
The output on my console is:
HeaderTextProvider: <CLKSimpleTextProvider: 0x78e3f800>
Body1TextProvider: <CLKSimpleTextProvider: 0x78e4eb30>
Body2TextProvider: <CLKRelativeDateTextProvider: 0x78e4f050>
TimeEnt: [<CLKComplicationTimelineEntry: 0x78e4edd0> date = 2016-03-21 05:00:00 +0000, template = <CLKComplicationTemplateModularLargeStandardBody: 0x78e4edf0>, animationGroup = (null), <CLKComplicationTimelineEntry: 0x78e4f520> date = 2016-10-01 17:00:00 +0000, template = <CLKComplicationTemplateModularLargeStandardBody: 0x78e4f540>, animationGroup = (null)]
TimeEntCount: 2
Why time travel isn't working the way you expect:
Time travel only supports a 48-hour sliding window. Any timeline entries outside the complication server's latestTimeTravelDate will be ignored.
When constructing your timeline, do not create any entries after this date. Doing so is a waste of time because those entries will not be displayed right away.
You can't time travel over six months ahead to October 1, so your Mar 21 entry would never change to show the October 1 entry.
Other issues:
You probably don't mean to iterate through every body object for each header object.
You also want to start with an empty entries array within this method, so you're not inadvertently appending to an array with any existing (backwards) timeline entries.
Change your loop to look something like this:
// Unwrap optional arrays. You can also check counts if there's the possibility that they are not equally sized.
guard let headers = headerArray, texts = body1Array, dates = body2Array else { return handler(nil) }
var entries = [CLKComplicationTimelineEntry]()
for (index, header) in headers.enumerate() {
let text = texts[index]
let date = dates[index]
...
}
print("TimeEntCount: \(entries.count)")
This will give you headerArray.count timeline entries, instead of headerArray.count x body1Array.count x body2Array.count entries.
You may also want to specify the type of objects in each array so you don't have to constantly use as!. This will provide type safety and let the compiler type-check your code.
It also might make your code more readable and maintainable if you keep the data in an array of structs (with header, text, and date properties), instead of using multiple arrays.