New Outlook Bug List

Well, mostly NOM

Because Outlook’s in-app bug support doesn’t let you submit a new issue until the old/current issue is closed, here’s a list of things I have seen (not including the big things like no contacts/notes/tasks functionality):

  1. The only indication for unread emails in a folder is the number. There’s no bolding or other indication (NOM)
  2. If a folder has subfolders with unread emails, and the higher level folder is collapsed/closed, there’s no indication of unread emails within (NOM)
  3. .olm file export appears to be broken, as Outlook can’t read its own .olm files (NOM and old Outlook)
  4. I could be wrong, but it looks like you can’t export in NOM at all
  5. The Scripting Dictionary has no knowledge of “pure” O365 accounts at all. (NOM and old Outlook) https://outlook.uservoice.com/forums/293343-outlook-for-mac/suggestions/38964790-update-the-scripting-dictionary-specifically
  6. Can’t change description of any account using the new sync engine (NOM) fixed in 16.33.19120904 Insider fast build
  7. O365 accounts created with NOM don’t exist for old outlook if you switch back. (NOM & old Outlook)
  8. Folders in accounts using the new sync engine don’t update/sync until you click on them. This seems to be a new bug introduced in the latest build or one/two builds before it. (This is mostly fixed by having the server unread counts update independently of the downloaded contents of the folder. It’s not EVERYTHING, but it’s better than nothing, and about 80% of what I care about for this issue, so I’ll take it.)
  9. Accounts using the new sync engine don’t have a “resend” option for sent email. This one’s a pain.
  10. In NOM, if you have multiple messages selected, and the TOP messages in the list shows as read, cmd-t (mark selected as read) doesn’t work. If the bottom or any other messages are marked read, (contiguous or not), as long as the TOP messages shows as unread, then cmd-t works as expected (marks all selected messages as read.) fixed in 16.41 (20080203)
  11. Closing the message window is destructive in that it blows out all your window size/position/folder list width/message list width/reading pane width settings. This isn’t documented anywhere, and there’s no warning. I doubt this will ever get fixed as from what Outlook support says, this is a design decision, and they tried to pitch it as standard behavior in macOS apps. But if it gets enough votes on uservoice, then maybe they’ll fix this nonsense. https://outlook.uservoice.com/forums/293343-outlook-for-mac/suggestions/39358999-stop-having-the-close-window-button-in-outlook-be (This appears to be fixed in 16.41. Which is good, because I think it’s the only bug on the list that’s been fixed)
  12. There’s no “view source” option for messages, a critical issue if you’re trying to resolve email errors/issues.

As I find more/these get fixed, I’ll try to update this list.

On Wasted Features

Outlook Mac Templates could be so good. Yet they barely exist.

As you can guess, this is about email templates in Outlook for Mac. Specifically in NOM, but I doubt it’s much different in “Old” Outlook. As they exist right now, well, I don’t know why they exist. They don’t seem to do much of anything but make resending an email…not easier than forwarding it. I mean, as soon as the “resend” makes it back into Gmail and O365 accounts in NOM, the entire advantage of templates as they exist goes out the window.

There’s a number of reasons. First, UI for using them is the Finder. Which is not ideal for a number of reasons, but the biggest one is that because Outlook includes no QuickView option for Outlook Templates, you can’t see what one looks like before you use it:

I hope you use really descriptive names

That’s the entire UI for email templates in Outlook for Mac. The Finder. And that’s kind of well…halfassed. Which is why almost no one uses them. Forget where you saved that template? Hope you enjoy spotlight searches or locate in the command line. Or command line find. Because Outlook won’t be of any use.

Oh, and of course, any templates you create on one machine won’t be seen by another unless you’re using OneDrive or iCloud. Even if you use OneDrive, it’s not like Outlook on the Web can use templates worth beans. I mean, if you go into the options, you can enable it, but creating them is non-obvious, and I’ve no idea, nor am I inclined to spend time searching, to figure out where they are saved even if I spend the time figuring out how to create them.

I’ve no idea about Windows, I don’t use Outlook for Windows, I’m much happier that way. And of course, templates don’t seem to exist for Outlook Mobile. So, as best I can tell, templates are only really usable on two of Outlook’s platforms, barely exist on a third and don’t exist at all on two more.

The point is, templates, to be useful have to be obvious, discoverable, predictable, and shareable. Outlook’s implementation currently fails at all of these. That’s a shame because if they added things like a proper UI for them with solid previews, made them accessible for all versions of Outlook for a given O365 account, then they might be better than “resend”. They’d be very useful for orgs that have specifically formatted emails they need to send out (and no, Word is not the way around this), etc. Templates, done well, are useful things.

Which as far as I can tell, is only available on Outlook:Mac anyway.

Really, given how barely there they are, if the Outlook team wanted to clean up some unused stuff, templates would be an easy target. I doubt their absence would actually create any extra work, and were OWA and the other versions of Outlook not on the Mac implement a “resend” feature, most of the use of templates would go away even further.

I’d rather they fix it and make it useful, because there’s a lot of good use space for templates, especially given O365, but if they aren’t, well, deleting unused code is also good.

Why Hiring Is Awful, ATS Edition

These Are Awful Things

There has been a lot, a lot of bits spilled about how awful getting hired in Tech is. It’s not better anywhere, by the way, I’ve a friend wanting to get work as a plant manager. It’s just as awful for him. My son’s trying to get an entry-level gig not in tech. Awful for him too.

A bit part of the reason is the ATS system, the systems that, in theory, automatically ingest resumes and help automate and streamline the hiring process. There are two parts to ATS, the front end, the bits that the applicants deal with and the back end, or the HR side. This post is mostly talking about the front end. I’ve some experience with the back end side of things, and there are parts of that which work really well, like applicant tracking, where someone is in the process, etc. That part works great. The rest?

Failure

So in talking about ATS systems, you have to include the application websites. There’s no way, from the applicant point of view to separate them, so I shan’t. They fail. They fail in ways that honestly beggar the imagination. No really, they do. For example:

  • Job Title fields that only allow for 20 character title names. In some cases for references. I’m sorry, are you paying for storage by the byte?
  • Company name fields that cannot handle a slash or a parentheses. One of my not-too-long ago gigs has a slash in their name. That’s not optional, that slash is part of the name of the company. I have seen so many ATS systems implode trying to ingest that. This is not 1989 anymore, unicode is real.
  • If you have an Associate’s degree in something that isn’t what the ATS allows for, good luck entering that.
  • If you have college experience, but no degree, there are any number of ATS systems that don’t allow for that, so you either don’t include that experience, or you lie and put down a degree you don’t have because you can’t leave the field blank and “no degree” isn’t an option. Morton’s Fork should not be a design philosophy for any system.
  • I know it seems odd, but ten years later, I do not remember the day I started at a gig. Yet for too many companies, that’s important.
  • Why can’t I paste my email address in when creating an account? What is the benefit of this kind of thing?
  • Why is the county in which I live important?

I could go on, but you get the point. These systems are not only designed badly, they don’t work well, and that is perhaps not the impression one should get from a company that presumes to be competent at its job. Or anything.

The worst part of course, is that there’s no penalty for the companies that do this. Like ghosting, the company is allowed to be nigh-completely incompetent at all parts of hiring, but if the applicant misuses an apostrophe, well, they’re completely unqualified to be employed at any position whatsoever. One side must be perfect the other side doesn’t even need to show up.

I literally have yet to see, as an applicant, an ATS that ingested a resume correctly, or even mostly correctly. Ever. To the point that now, people recommend you basically create your resume in vi or some other text editor, solely, solely to increase the chances of the ATS system not losing its addled mind over you using a form of word processing created after 1990 or so.

I get that the assistance in tracking an applicant’s status is important. That part, in my experience works well, but it is literally the only part of every ATS that I’ve worked with or interacted with that works well. The rest is all hot garbage and should be buried next to the E.T. video game carts.

A Quickie on New Outlook Search

As I wrote in my first writeup on NOM (New Outlook Mac), I talked about the new search engine, and how it built KQL queries that were compatible with Outlook’s Web version. I just tested it, and yes, it is. Even cooler, if you modify a search in one and paste it into the other, it just works.

For example, I built this search string in NOM:

from:apple.com AND to:jwelch@bynkii.com AND subject:(AppleSeed) AND received>=2018-09-08

And got a listing of four/five emails from AppleSeed relating to various new builds. It pulled in results from multiple folders on that account, including the deleted items folder, which the old search didn’t do, I had to go into that folder to reliably search it.

I then opened my account in OWA, and pasted that string into the search bar there. Booyah! So then, I changed one of the terms from (AppleSeed) to (tvOS) and pasted that back into NOM. Booyah, now I only get emails with tvOS in them.

So that’s pretty cool, but what occurs to me is that this opens up some support avenues for people. If you’re really good with KQL and you have a user trying to do a really specific search and they’re having a hard time with it, you could build that search for them, send them the string, then they can just paste that into the search bar and voila! They find the thing they need.

Now, extending this, were say, the Outlook team to update the scripting dictionary a bit to allow for pasting from the clipboard, then you could provide this to the user as a script and they wouldn’t even have to manually copy and paste. They run the script and voila! They find the thing.

But wait, there’s more. So again, what if the Outlook team updated the scripting dictionary so that you could search more than just contacts with the new KQL search without pasting. And what if they allowed you, via the updated scripting dictionary to save that search as a saved search in the appropriate section (mail/calendar/contacts). That would be kind of handy if you’re having to move someone to a new machine, or you have some standard searches that people in a given group do a lot. Conversely, when they moved to a new machine, you could transfer any existing saved searches via script pretty easily.

You’d be able to give someone a new machine with all the saved searches they built, along with any searches you normally install. That would be kind of nice to be able to do for your users. And you could…if the Outlook Team were to update the scripting dictionary. (This is what I think about on the bus from the show location back to my hotel. Because I’m not quite right.)

Even better, you could use this to help build a maintenance/assistance script that would help someone look for “missing” emails/calendars/etc., create your own search scripts that could then take other actions based on the results of a search, search for specific attachment files and have other applications take actions on those files…if the Outlook Team were to update the scripting dictionary that is.

I feel there’s a theme here, but I can’t quite put my finger on it…

The Outlook is good

Outlook:Mac’s biggest update…well, ever

At Microsoft Ignite 2019, the Office team released an early-ish beta of the new Outlook client. If you’re on the Insider Fast track, you should be able to get it by the time this is posted.

As some background, I’ve been a part of working with and testing Outlook on the Mac since it was called “Entourage” and really, since Entourage was called “Alpaca” because it didn’t yet have a name. And yes, I know, Entourage and Outlook are in fact different products. Relax, it’s all good. This is just establishing timeline of how long I’ve been working with and help test this product.

I don’t think it’s hyperbole to say this is the biggest update to Outlook on the Mac since 2011. Possibly since the short-lived Outlook 98.

At the heart of these changes is the “new” sync engine. I say “new” because it’s not really new, it’s the same sync engine used in other Microsoft products, such as Outlook on iOS and the mail and calendar clients in Windows 10. Yes, that’s right, EWS is dead. Completely dead. It is an ex-protocol.

According to the team, this is really important, because it allows them to radically increase the “improvement velocity” of Outlook. This was a problem when Outlook was using EWS, as that protocol was considered “done” and wasn’t going to get new features anytime soon.

In terms of immediate benefit, I’ve seen two: first, prior to New Outlook Mac (NOM…DON’T YOU JUDGE ME), if I replied to an email in Outlook, that message would show that I’d replied to it. If however I replied say, from iOS’s Mail app, then Outlook wouldn’t show that. Not a huge deal in and of itself, but that kind of linkage in the UI is really convenient. With NOM, even when you reply from iOS Mail, Outlook will reflect that.

I was straight. up. giggling over that. Because it removed such a silly pain point.

In addition, Outlook’s resource usage has dropped off a cliff. I was used to seeing Outlook, regularly, on my 2019 MacBook Air, eating over 100% of available CPU in Activity Monitor. With NOM, I have to search for Outlook in Activity Monitor. At most, I’m seeing around 40% or so. Disk I/O looks to be similarly improved. That’s a big deal for people who care about battery life. Like, you know, everyone on a laptop.

In terms of syncing, and keep in mind, this is a very early release. For example, on-prem Exchange and iCloud accounts still use the old sync engine. I plan to test it once I’m back home and on stable Wi-Fi so I can give y’all some real-world numbers.

UI Changes

The UI has had a fairly extensive change. First, the Ribbon is gone. Like gone. As with EWS, it is an ex-UI element. I personally never had a real problem with the Ribbon, I honestly kind of like it, but I’m a very edge case users. (Long ago, I had some weird stuff using rules kicking off scripts as mailing list folder reapers. I am not an average user.) The new UI is very clean and nicely customizable:

New Toolbar. No More Ribbon in NOM

Obviously, NOM supports Dark Mode. The new toolbar is also fairly customizable:

Toolbar Customization Options

The look is really clean. One thing I’d like to see change in upcoming versions is an ability to increase the font size for toolbar labels. Some of us are not gifted of perfect vision.

Search changes

While Outlook’s search has been solid, there were a lot of cases where it would miss things. As well, being based on Spotlight, if you added a new account, there was a delay before you could use the search effectively while indexing was happening. With NOM, Outlook is now using the MS Online search engine instead of Spotlight. Along with what they say is better overall search performance, there’s a few neat new features this brings:

First, the search is supposedly faster and better. I’ve not had a chance to test this out, not extensively, but it doesn’t feel any slower. It’s also fully usable much faster than the Spotlight-based search was. The search bar itself is now much larger and central in the window. Secondly, they’ve also created a nice in-between UI for when you need a bit more than just typing in a text field, but you don’t need the full-on advanced search.

New search bar, still supports “classic” email search syntax
New search dropdown

The new dropdown is accessed by the funnel icon in the search bar. But I want you to notice the search syntax in the search bar over the dropdown. I’ve pasted it in below:

from:events@floridatheatre.com AND From: "Florida Theatre" AND subject:(yourself) AND received>=2019-10-08

That syntax is the same syntax you’d use in Outlook on the Web. That’s kind of handy. It’s based on MS’s KQL language. This is a fairly rich query language that allows for far richer searching than the traditional searching one did pre-NOM. The way the drop-down builds the query is kind of neat and once the full advanced search UI is attached, (Neither it nor saved searches are currently functional in this version), there’s a potential to do some neat work in building custom searches for people.

Another UI change is related to conversation. Now, when viewing by conversation, it pulls in emails from all folders, not just the current one, a convenience for those with complex folder structures (like me, which is ironic, as I never use conversation view.) There is also an “Ignore Conversation” option, but be aware, “Ignore” in this context moves the conversation into Deleted Items. This is kind of suboptimal, and could be problematic, and I would like to see this behavior change moving forward. In the current release, you can’t not use conversation view, something that again, is a beta issue.

(Note: This really is a beta, not just a “sort of not done” drop. There’s a lot that needs to be done, so NOW is the time to get your feedback in, things are still fluid.)

The Return of My Day

The fogies among the Outlook:Mac users will remember My Day, a related, but separate application that showed you, well, your day. It was a floating window that showed you what was going on in your calendar for that day. I really liked it and have missed that feature since it went away with Outlook 2016. Is it a real pain to alternate between cmd-1 and cmd-2 to switch between mail and calendar views? No, not really, but it’s always been one of those “why is this necessary” things. Like being nibbled to death by baby ducks.

With NOM, My Day is back. It’s not a separate window, it lives on the far right of your mail window and can show you your agenda or your day:

Agenda view (L) and Day View (R)

This is one of those things that isn’t really necessary, but it saves me a lot of time over the course of the day a few seconds at a time, and it means I don’t have to switch out of my current context just to see what’s happening next in my calendars. My Day can show you as many or few of your calendars as you like.

Talking to some of the Outlook folks, they’re looking at, in a future update, making My Day accessible via the Menu Bar, so you could see your appointments and schedule without having to open Outlook. This would be nice, especially when you’re working on something that isn’t email and you don’t want to get drawn into email just because you need to check your schedule.

Another new feature, not yet wired in, is the ability to reply to a message without having to pop a new message window. Calendar invites have a new UI too, giving you the information you need right there in the invite window:

New Event UI

At some point in the near future, this will integrate with Teams, so you can use that as an option for a meeting in addition to or instead of physical rooms. After the session, a few of us asked for more intelligent Out Of Office statuses, like “Working Somewhere Else”, which would allow someone who was working at a different location to, when they get the meeting invite, not even see a physical location for the “location” but rather the “Join Teams Meeting” button. Creating the event would also use this, so that the event creator would know that some of the meeting attendees would not be able to be there in person, and would be using Teams. A bit nicer than the blunt weapon that “out of office” currently is. (Again, if this is something you’d like to see, get your feedback in now.)

Calendar UI changes

Along with the mail UI, the calendar UI has gotten a lot of love. The default toolbar has been greatly simplified:

New Calendar toolbar

Note that the Temperature gets front and center, with the Day/Week/Month view picker in a dropdown to the right. The widget next to that allows you to view My Day in the calendar as well. I’m not sure how amazing that is, but sure, why not. Actually, I accidentally figured out why this is cool. You can have a different set of calendars displayed in My Day than you have displayed in the main calendar window. That’s cool.

Event creation has been greatly simplified in NOM. For simple events, you just drag and drop in the calendar window to select your meeting times on a given day, ala the macOS Calendar app, which then gives you a popup that allows you to quickly set up a simple meeting:

Quick Meeting Popup

A change that I really appreciate is the much, much easier methods NOM has for changing calendars for a new event. It’s literally just a pulldown in the top of the new meeting popup:

SO MUCH NICER

It’s pretty simple and works well for simple needs. But if you need more, the “real” event creation window has also gotten a revamp:

Full Event Creation Window

The easier calendar selection is there as in the popup. Also note that the repeating settings have been moved to a more central location, rather than up at the top where it was a bit easy to miss. Teams meeting selection is right there as well. One thing of note is the calendar column on the left. Note that the current schedule shows as green. If that time conflicted with another event, that would be red, (and I did suggest, given the prevalence of color blindness in the population, that Outlook not solely rely on color for this functionality.) You can change the meeting time and duration in that window to help you find a more compatible time if needed. The traditional scheduling assistant is still available for when you have a lot of attendees with complicated schedules.

One thing I want to point out is the little sun icon in the upper right part of the main window next to the calendar column. That’s in the email window as well, and it allows you to easily toggle the text area of the meeting (or message) from a dark background to a light background and back. A nice touch.

Again, this is a really early release beta. There’s a lot of work to still be done. For example, the Contacts pane doesn’t work at all. You click on it, you get a sheet telling you you have to switch back to old Outlook for that. It is very much not done.

But I’m still really impressed with where NOM is going. The new sync engine will enable a lot of things that just weren’t going to be possible before, like voting. Better syncing with iCloud was brought up and they are aware of the need, and from what the Outlook folks were saying, this should be significantly easier to implement. (Given the Google account improvement updates, I think any political issues went away some time ago.)

There’s some other missing features, like “resend”, but that should be coming back. “View Source” is also missing with accounts using the new sync engine and I pointed out that while not used by a lot of people, for user support/mail support people, being able to see the raw headers of an email is a critical need. Again, if this is important to you, let the team know.

One interesting statement during the session was that matching the Windows client feature for feature is not a goal in and of itself. (Obviously, there’s no ribbon any more.) Before everyone gets all weird, there are windows features, (Like VBA scripting) that not only don’t make a lot of sense on macOS, but are actually dangerous, (like VBA scripting.) “It’s in Windows” is not a great reason to implement a feature. “This is a thing I need and my users need because <reasons>” is a better way to ask for a feature.

I did remind them that Outlook’s scripting implementation has been stagnant since 2011, and it shows and they should update that. Additional feedback on that would be a help in getting them to up the priority for that.

To use NOM now, you currently need to a) be on macOS 10.14 or later and b) be on the Office Insider Fast Track. The Insider Fast requirement will eventually go away, the 10.14 and later, I don’t see that changing.

NOM is not done yet, there’s still a lot of oddities because of its unfinished state, but it’s like the pause between the time a pilot shoves the throttles into afterburner, and the plane starts hauling ass. For the first time in a while, there’s a clear path for some real improvements in Outlook, and I am so here for it.

From AppleScriptObjectiveC to Swift pt. 2

Today kids, we get some REST…

Okay, the joke was right there, and no, there is no joke “too bad” for me to take. So in the previous post, we looked at initial defaults setup, both in ASOC and Swift. We didn’t do anything with them, but we have the initial setup done. There’s a few things we have to deal with outside of that before we actually get into defaults or UI setup.

Also, for everyone who says “just rewrite it in <different language I like better>, note that this series will be rather long before we get to what the ASOC version has today. There’s going to be a lot of work done to get to where we are right now. No new features. Just a lot of work to get to where we are today.

Nagios Manager interacts with Nagios servers via a REST API. So I send URLs with data and get data back. Sometimes I’m reading existing information, (GET), creating or adding information (POST) or removing information (DELETE). But everything revolves around that.

Within Swift, there’s two major components I’m using, at least initially: URLSession and JSONSerialization. The former handles the communication between the app and server(s), the latter handles parsing it out into a useful format, usually a dictionary, or NSDictionary in ObjC-land. While I do use JSONSerialization in ASOC, for the URLSession components, I “cheat” as it were, and use a handy trick from AppleScript: the “do shell script” command, which lets me just execute curl commands from the shell.

I could use URLSession within ASOC, but I never did, “do shell script” is simpler, although probably not easier, and it lets AppleScript handle some things for me automagically. So as before, I’ll split this post up into the ASOC code and the Swift code, with explanations of each. This post will be focused on the GET part of the communications, we’ll take on POST and DELETE later on.

Also, while one downside of Swift is that it does’t have a tool like Script Debugger, it does have playgrounds, which gives me some of the tools Script Debugger has. (There’s no proper debugging in playgrounds, and that is an actual shame, it would make them even more cool than they are.) Playgrounds are awesome for testing out code before putting it in the app.

Nagios REST URLs

Before we get into the code, an overview of how URLs are used within the Nagios REST API. Regardless of what a given URL is doing, they’re all broken down about the same way. There’s a “root” URL, a path that indicates the main section of what the URL is for, an API Key, and then the specific command properties. GET/POST/DELETE are used to indicate the primary action. In this example, I’m using a ‘system info’ URL, as it returns a small amount of data that’s easily used to ensure I’m getting the info I need in the correct form. By and large, this is a very large string that I end up coercing/converting into a Dictionary (Swift) or a record/NSDictionary (ASOC) so I can make better use of key-value pairs.

Here’s a sample of the URL, (sanitized, obvs):

http://<ipaddress or DNS name of server>/nagiosxi/api/v1/system/info?apikey=<very long, complex API key>&pretty=1″

That’s pretty simple, but it has the basics. In terms of application logic, there’s a handful of parts. The first is the IP address or DNS name of the server. I trust I don’t have to explain that one. The second is the /nagios/api/v1/ part. that will go after the IP address or DNS name of the server for every command, and we’ll use that as a constant in Swift, once we get going. The system/info? bit identifies what part of the API the URL is talking to. In this case, basic system info. For user functions, we’d use “system/user”, and for host functions, “objects/host”. The API key is the authentication mechanism for a given server, and why I may add one new feature into this, if it’s not that hard, namely encrypting that data. Maybe. (Security in monitoring software is a mess on a good day. But if I can manage to do the right thing, I try to. I’m not a very good programmer.)

Everything after the API key is the actual command parameters. In this case, there are none, one reason it makes for a useful test case. Finally, the “pretty=1” bit makes the return look like this:

{
“product”: “nagiosxi”,
“version”: “5.6.5”,
“version_major”: “5”,
“version_minor”: “6.5”,
“build_id”: “1563463555”
“release”: 5605
}

Instead of a single undifferentiated string. Much easier to read and use, especially for setting up key-value pairs.

ASOC communications

Here’s the basic code for sending a GET request and putting the results into a record (dictionary) in ASOC:

property theSMTableServerURL : "" --bound to server url column in table
property theSMTableServerAPIKey : "" --bound to server API Key column in table
====================================================================
set theSMSURL to theSelectedServer's theSMTableServerURL as text
set theSMSURL to current application's NSString's stringWithString:theSMSURL
set theSMInfoURL to theSMSURL's stringByAppendingString: "system/info?apikey="
set theSMServerInfoCommand to "/usr/bin/curl -XGET \"" & theSMInfoURL & theSMSelectedAPIKey & "&pretty=1\"" 
set theSMServerInfoJSONDict to my getJSONData:(theSMServerInfoCommand)

====================================================================
on getJSONData:theCurlCommand	
	set theReturnedJSON to do shell script theCurlCommand
	set theReturnedJSON to current application's NSString's stringWithString:theReturnedJSON
	set theReturnedJSONData to theReturnedJSON's dataUsingEncoding:(current application's NSUTF8StringEncoding)
	set {theReturnedJSONDict, theError} to current application's NSJSONSerialization's JSONObjectWithData:theReturnedJSONData options:0 |error|:(reference)
	return theReturnedJSONDict
end getJSONData:

There’s really not a lot going here. The three sections offset by the “===” line are both parts of using curl in ASOC and in the app.

The first part is setting up the two properties we use. The server info, including URL and API key are in the server manager table in the UI. (Really, it’s in an array controller, but there’s no real code to demo that bit. At least not yet.)

theSMTableServerURL property is the IP address or DNS name of the server with “/nagiosxi/api/v1/” appended to it. All Nagios REST commands use that, so there’s no point in not storing it that way. Saves time.

theSMTableServerAPIKey property is the API key used for that server, also in the server manager table.

set theSMSURL to theSelectedServer's theSMTableServerURL as text

Is me doing two things. First, I’m avoiding manipulating the property directly. Secondly, I’m making sure I know theSMSURL is text. (AppleScript’s version, not Cocoa’s.) I found that ASOC has an annoying tendency to sometimes make this particular value text and sometimes it’s an NSSTring. The behaviors of both are rather different within ASOC, so I remove the uncertainty.

set theSMSURL to current application's NSString's stringWithString:theSMSURL

Is me converting AppleScript text to a proper NSString. We’ll need that, and again, this way I know what SMSURL is in terms of type.

set theSMInfoURL to theSMSURL's stringByAppendingString: "system/info?apikey="

Since NSString isn’t mutable, and NSMutableString gives me the irrits and this app is not time-sensitive in terms of execution, meh, create another NSString, but with the bits that make it a “I want basic info on the Nagios Server” command, along with the APIKey lead-in.

set theSMServerInfoCommand to "/usr/bin/curl -XGET \"" & theSMInfoURL & theSMSelectedAPIKey & "&pretty=1\""

Here’s where we build the full curl command. Since the command itself as used requires quotation marks in the command, we add those in by escaping them. the “&” is AppleScript’s string concatenation symbol. So when this is all done, theSMSServerInfoCommand is fully set up to send a request for basic server info to the Nagios server.

set theSMServerInfoJSONDict to my getJSONData:(theSMServerInfoCommand)

Here’s where we call the getJSONData function, or in AppleScript-ese, the handler. We’re passing it theSMServerInfoCommand string, and getting back theSMServerInfoJSONDict, which will be an NSDictionary, or AppleScript Record.

Now for the function. There’s not a lot of code, but there is some stuff happening. ASOC, as a rule, is slaved to Objective-C, so the calling conventions vaguely resemble ObjC, but without all the brackets and @-signs. I think it’s a bit more readable.

on getJSONData:theCurlCommand

The initial function definition. Note that we don’t have to specify the type of data theCurlCommand is. It’s just there, we either use it correctly or we do not. Handy in some ways, less-handy in others. Definitely lets you be lazy, so I like it. Life is too short for a high-level language not to do work for you.

set theReturnedJSON to do shell script theCurlCommand

This is the “do shell script” command, which lets us run curl in the shell environment. It uses sh, not bash or zsh. That can trip you up. It also doesn’t use your .profile or .zshrc files, so you really want to fully path everything. Really. Really.

The result of the command, assuming no error is set into theReturnedJSON. It may not actually be JSON, strictly speaking, but I have my theOwn theNeuroses with variable naming, and I’m the only one having to read this, so meh. What you get back looks like this:

{
“product”: “nagiosxi”,
“version”: “5.6.5”,
“version_major”: “5”,
“version_minor”: “6.5”,
“build_id”: “1563463555”
“release”: 5605
}

(just as a reminder)

set theReturnedJSON to current application's NSString's stringWithString:theReturnedJSON

Since do shell script is an AppleScript command, it returns AppleScript text. We need it to be an NSString, so we convert it to one. Since AppleScript is somewhat unconcerned about many types of coercion, we don’t have to use a different variable. theReturnedJSON used to be AppleScript text, now it is an NSString.

set theReturnedJSONData to theReturnedJSON's dataUsingEncoding:(current application's NSUTF8StringEncoding)

since NSJSONSerialization wants NSData and not NSString, we have to convert it to NSData here via NSString’s dataUsingEncoding method and NSUTF8StringEncoding as the specific encoding process. We do use a new variable here, so I know this is the NSData version of theReturnedJSON.

set {theReturnedJSONDict, theError} to current application's NSJSONSerialization's JSONObjectWithData:theReturnedJSONData options:0 |error|:(reference)

This returns an NSData record of arrays and shoves it in theReturnedJSONDict. Technically it’s an NSJSON object, but really, it functions more like an NSDictionary. Each line is a key-value pair, and this also allows it to work like an AppleScript Record, which is really handy for ASOC.

return theReturnedJSONDict

return theReturnedJSONDict to the calling function.

end getJSONData:

End the function/handler. the “:” functions as a “()” would in other languages. This is inconsistent mind you, sometimes, you’ll see “foo:()” or even “foo()“. The ASOC bridge is sometimes a tad odd.

Swift Communication

So some caveats here. The Swift code is very much playground code. It will look different in the final version, and once I get to where I feel like setting up the github repo for it, you’ll be able to see that. Again: I am not a full-time programmer, nor am I a Swift genius. I am very much a pickup truck programmer. It ain’t fancy, probably slower than it could be, but I can read it and understand it. It’s similar to how I play D&D: I understand the tricks I use as a monk very well. I don’t use a lot, but the ones I use, I really understand. Basically, I’m not clever. At all.

Also, many, many thanks to https://learnappmaking.com/urlsession-swift-networking-how-to/ for that page. It made it so much easier to figure out WTF was going on.

import Cocoa

//many thanks to https://learnappmaking.com/urlsession-swift-networking-how-to/ for this info.

//build the components to get data from nagios
//you can't use URLComponents with this, the encoding causes problems.

//we may be using url = url?.appendingPathComponent("users") kind of thing in the future

//this returns a small number of things from the nagios server
//we'll probably want this to be a var when we build it, made up of different items.

let theURL = URL(string: "http://(ipaddress or DNS name of the server)/nagiosxi/api/v1/system/info?apikey=(really long API key)&pretty=1")!

//create a URL Session object
let theSession = URLSession.shared

//theTask creates a data task. The parts are as follows:
//data - the actual JSON data we get from the server
//response - various response codes, mime types, etc
//error - any errors thrown
let theTask = theSession.dataTask(with: theURL) {data, response, error in
	
	//if we get an error or no data, bad. Probably won't use this in proudction
	if error != nil || data == nil {
		print("Client error!")
		return
	}
	//check for valid response code. Again probably won't use this
	guard let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else {
		print("Server error!")
		return
	}
	
	//check for the right mime type. Since we know how nagios returns things, not really needed
	guard let mime = response.mimeType, mime == "application/json" else {
		print("Wrong MIME type!")
		return
	}
	
	//this parses the returned data and turns it into  a proper JSON object
	do {
		//store contents of theURL as a Data variable
		let theData = try Data(contentsOf: theURL)
		
		//get the results as a dictionary, not a string
		let theJSON = try JSONSerialization.jsonObject(with: theData, options: []) as! [String:Any]
		
		
		//note that release version comes as an int from the rest api, not a string:
		/*
		{
			"product": "nagiosxi",
			"version": "5.6.5",
			"version_major": "5",
			"version_minor": "6.5",
			"build_id": "1563463555",
			"release": 5605
		}
		*/
		
		//set up the variables to extract the data from the dictionary
		var theProduct: Any
		var theBuildID: String
		var theVersion: String
		var theRelease: Any
		var theMajorVersion: String
		var theMinorVersion: String
		
		//pull our vars from the dictionary
		//since these could be nil, we force unwrap at theJSON and use as! to force downcast AnyObject to string
		//or Int as needed
		
		//it is probably a good idea to make sure your variables are what you think they are
		//this can avoid problems later. But this does show you have options if you're not sure
		//what the return will actually be
		theProduct = theJSON["product"]! //as! String
		theBuildID = theJSON["build_id"] as! String
		theVersion = theJSON["version"] as! String
		//even though we created the var as an Any, we force it to be an Int here. 
		theRelease = theJSON["release"] as! Int
		theMajorVersion = theJSON["version_major"] as! String
		theMinorVersion = theJSON["version_minor"] as! String
		
		print(theProduct)
		print(theBuildID)
		print(theVersion)
		print(theRelease)
		print(theMajorVersion)
		print(theMinorVersion)
		
		//print(theJSON)

}

theTask.resume()

There’s a lot going on here. Well, not really, but it took me a while to get it. (That was reason why I didn’t use NSURLSession in the ASOC version. It was weird to learn. Well, also, I didn’t have to.)

Some of it, like the setup for the print statements is test code, so I know what’s going on with it. That most likely won’t be that way in the final version, but it is useful here.

let theURL = URL(string: "http://(ipaddress or DNS name of the server)/nagiosxi/api/v1/system/info?apikey=(really long API key)&pretty=1")!

Since this is a constant, we use “let”. This will eventually be built the way it is in the ASOC version. But for now, we just hardcode the whole thing.

let theSession = URLSession.shared

Create a simple URLSession object. Should I need to get more complicated, I will, but not until.

let theTask = theSession.dataTask(with: theURL) {data, response, error in

Create a data task that when run, gives us three things:

1) data, the actual returned JSON data
2) response, the HTTP response info wif error != nil || data == nil {ith things like return codes, etc.
3) error, I feel this is self-explanatory. At best, we’ll only care if it’s not nil.

Maybe not even then. Nagios Manager isn’t the kind of thing you just run casually. I can make more than a few assumptions.

if error != nil || data == nil {
print("Client error!")
return
}


If we get any kind of error or no data, print an error message and return.

(can I just say wordpress’s block editor handling of returns is annoying as hell. No you goddamned schmuck, just because there’s a return when I paste something in, that does NOT mean I want it in a separate block. Ye gods.)

guard let response = response as? HTTPURLResponse, (200…
299).contains(response.statusCode) else {
print("Server error!")
return
}


if we don’t get a “correct” response code, do error things

guard let mime = response.mimeType, mime == "application/json" else {
print("Wrong MIME type!")
return
}


If the MIME type is wrong, do error things.

do {
//store contents of theURL as a Data variable
let theData = try Data(contentsOf: theURL
let theJSON = try JSONSerialization.jsonObject(with: theData, options: []) as! [String:Any]


Here, I’m setting up “Data” as a Data object version of theURL. Since the “contents” of the URL is the returned data, which looks like a big string to swift, we want it to be a dictionary, with the keys being String and the values being Any, as we get multiple return types. This is kind of a pain, since we don’t get ASOC’s “I’ll handle that for you” features, but it’s not awful. (I may be wrong about what this code does. But it does work the way I expect it to work, so close enough. URLSession is weird to me.)

//set up the variables to extract the data from the dictionary
var theProduct: Any
var theBuildID: String
var theVersion: String
var theRelease: Any
var theMajorVersion: String
var theMinorVersion: String


So out of all of these, only theRelease isn’t.a string, it’s an int. This is me playing a bit with Swift, because while I’ve read books and done stuff, this is the first time I’m actually using it.

//it is probably a good idea to make sure your variables are what you think they are
//this can avoid problems later. But this does show you have options if you're not sure what the return will actually be

theProduct = theJSON["product"]! //as! String
theBuildID = theJSON["build_id"] as! String
theVersion = theJSON["version"] as! String
theRelease = theJSON["release"] as! Int
theMajorVersion = theJSON["version_major"] as! String
theMinorVersion = theJSON["version_minor"] as! String


If you can’t tell, I talk to myself a lot in comments. I also walk my way through problems that way. Sometimes I put recipes in my comments. I’m odd like that. This is just me extracting things from a dict and documenting it so when I do it later, I know how. Note me coercing things. Except for theProduct, which was a “I wonder what will happen if I leave it as Any. Turns out, not much. But I won’t do that in the app, seems like a bad idea.

print(theProduct)
print(theBuildID)
print(theVersion)
print(theRelease)
print(theMajorVersion)
print(theMinorVersion)


PRINT ALL THE THINGS

} catch {
//print any errors
print("JSON error: (error.localizedDescription)")
}


Do error things.

}
theTask.resume()


Actually run the task, and get the JSON that lets us do the things. Y’all, I get that it works and it looks the same in ObjC, but this is kind of weird. Just saying.

As I said, this is not how it will look in the app, but it helps me see how swift does things. I’ll probably wrap up the JSON code in its own function and maybe even wrap up the URLSession code in its own function. Or not. Depends on how much of a pain in the ass this is.

The next bit of this will probably delve into the initial UI creation. Unless it doesn’t. And if you read this, thanks!

From AppleScriptObjectiveC to Swift

Oh this will be fun…

Recently, I decided to try to update my Nagios Manager app from AppleScriptObjectiveC to Swift. There’s a few reasons for this, one, it takes a lot less time to type “Swift” than “AppleScriptObjectiveC”. I mean, I can use the more common “ASOC”, but that doesn’t google as well. Secondly, there’s a few things I’d like to do that are a real pain in the ass to do with ASOC, and some that just won’t work.

I don’t want anyone to take “John is dumping ASOC” from this. I’m not. I like ASOC, and it’s a good higher-level language. But there are things it can’t do well, and Swift does those things well.

Note: I will not be using SwiftUI. It would take a month to get Nagios Manager’s UI working in SwiftUI, and in any event, if this were to ever move to iPads/iPhones, I’d have to redesign the UI completely for them anyway. For now, this is a macOS-only app, and as I’ve talked about before, SwiftUI is a really awful choice for macOS apps that aren’t also going to be on iOS/iPadOS

UserDefaults

Nagios Manager stores its settings in User Defaults. It’s a plist that’s an array of dicts with a boolean I use to see if there’s anything there other than the boolean. I’m kind of lazy and if I use UserDefaults, then I don’t have to deal with paths or locations. It may not be the best way to do this, but it lets me write less code. I like writing less code. The initial ASOC version is:

property theDefaults : missing value --referencing outlet for our NSDefaults object
property theSMSettingsList : {} --settings list array
property theSMDefaultsExist : "" --are there currently settings?

set my theDefaults to current application's NSUserDefaults's standardUserDefaults() --make theDefaults the container for defaults operations

my theDefaults's registerDefaults:{serverSettingsList:{}} --sets up "serverSettingsList" as a valid defaults key  changed to more correctly init as array instead of string. It also deals with nils much better

set my theSMSettingsList to (my theDefaults's arrayForKey:"serverSettingsList")'s mutableCopy() --this removes a bit of code by folding the NSMutableArray initialization and keeps it mutable even after copying the contents of serverSettingsList into it.

 set my theSMDefaultsExist to theDefaults's boolForKey:"hasDefaults" --get the boolean value for the hasDefaults key

 if not my theSMDefaultsExist then --if there are no defaults, let the user know this so they can fix that issue.

try --to catch the error -1 

display dialog "there are no default settings existing at launch" --my version of a first run warning. Slick, ain't it.28 when a user hits cancel

on error errorMessage number errorNumber 

if errorNumber is -128 then
--this error is generated when the user hits "cancel" in the display dialog. This on error sinkholes it that so it doesn't cause any actual problems, since the end result for either Cancel or OK is what we want.

 end if
 end try 

set my theSMStatusFieldText to "If you're seeing this, then there's no servers saved in the app's settings. This tab is where you add them.\r\rYou'll need three things - the server's name, URL and API Key. For the URL, only the first part, i.e. https://server.com/ is needed. The \"full\" URL is generated from that.\r\rThe app itself is pretty simple. You can add or remove servers. Those are saved locally on your mac.\rThose servers are used to pull down user info in the User Manager tab. More info is in the application help in the Help Menu."

else if my theSMDefaultsExist then --there's no point in running loadServerTable: if there's no data to load

 my loadServerTable:(missing value) -- initial load of existing data into the server table.

end if

Yeah, there’s a lot going on there, especially if you aren’t used to ASOC or AppleScript. Also, I really like to comment the crap out of my code.

The first three lines are setting up properties, which are…an odd form of global variable that’s not really a global. I probably abuse them, but for ASOC, properties are your very good friend. They’re initially set up as empty strings, one empty list/record which corresponds to an array/dictionary in Swift and one missing value, which probably comes closes to an NSObject. We mostly use them in ASOC for UI binding, but we use it here for creating our initial NSUserDefaults object.

One of the weird things about going from ASOC to Swift is that ASOC is rather tightly bound around ObjC (you can use ObjC in an ASOC app), but not Swift. So there’s some translational work you have to do there, like Array instead of NSArray, etc.

The next statement creates our UserDefaults object:

set my theDefaults to current application's NSUserDefaults's standardUserDefaults() --make theDefaults the container for defaults operations

There’s some ASOC-isms there that need to be dealt with. The “my” keyword is a way of dealing with scope. If I didn’t have the “my”, I’d have to have theDefaults defined in the function it lives in, on applicationWillFinishLaunching:aNotification specifically. (“on” is another ASOC keyword, ala “func” in Swift.) The “my” allows me to use the property version of “theDefaults”. Oh, and “--” is how you do single line comments in ASOC and AppleScript.

current application's” is another ASOCism, which relates to how generic AppleScript does things. AppleScript is really designed to be an application scripting language, where you use different applications as engines to do work. AppleScript in and of itself isn’t really designed to solve all your problems for you. So the “current application” thing is needed so the ASOC runtime knows what you mean. This statment is saying: “Set the value for theDefaults” to be an instance of NSUserDefaults.” This will be how we are able to use NSUserDefaults methods. This is why we defined theDefaults as missing value in the properties statement.

Next is:

my theDefaults's registerDefaults:{serverSettingsList:{}} --sets up "serverSettingsList" as a valid defaults key changed to more correctly init as array instead of string. It also deals with nils much better

This is one of those cases where a higher level language is much nicer to work with. I don’t have to declare much about serverSettingsList other than it’s some form of array-ish object. This will let it work as either a record (dictionary) or list (array). It’s kind of nice. I could have used a string or what have you here, but it turns out I need an array-ish object anyway, and it handles nils better than strings or other variable types.

Next is:

set my theSMSettingsList to (my theDefaults's arrayForKey:"serverSettingsList")'s mutableCopy() --this removes a bit of code by folding the NSMutableArray initialization and keeps it mutable even after copying the contents of serverSettingsList into it.

So within theDefaults, serverSettingsList, as it turns out, is an array of dicts, or in ASOC terms, a list of records. There’s a few dicts in there, one per nagios server and some for the AD Auth Records Nagios Manager needs to use. So what we do here is say “Shove all the data in serverSettingsList into theSMSettingsList array-ish thing, (which then makes theSMSettingsList a list of records), but also make it an NSMutableArray, because we’ll potentially need to modify it at some point.

Next we want to see if there are any defaults currently on-disk:

set my theSMDefaultsExist to theDefaults's boolForKey:"hasDefaults" --get the boolean value for the hasDefaults key

Now, I could have checked to see if the results of the mutableCopy() were nil, but, that’s only going to be the case if there’s nothing. Within Nagios Manager, there is the ability to delete one, or more, or all servers and settings for those servers. However, that doesn’t delete the defaults plist file. So we could have an empty array, which wouldn’t necessarily return nil, even though there’s no useful data in it. By checking the value of the “hasDefaults” boolean, I get a better answer, one of three:

  1. There’s a defaults file, but no servers listed (false)
  2. There’s no defaults file, (false/nil or the ASOC version of nil, missing value.)
  3. There are defaults in the file, (true)

That reduces down to one of two return values, true/false, which make checking easy. The next part is an if-then which handles this:

if not my theSMDefaultsExist then --if there are no defaults, let the user know this so they can fix that issue.

In Swift, this would be “if !theSMDefaultsExist”. If this happens on launch, there’s no defaults available to load, so let’s tell the user that. We’ll do that via a display dialog statement that will let the user know there’s no defaults to read. We’re going to wrap that in a try block because when you hit the “Cancel” button in a display dialog’s dialog, that generates an error (-128) that we want to trap and sinkhole, since we don’t really do anything based on the response. This is jsut for notification purposes to the hu-mon:

try --to catch the error -1

display dialog "there are no default settings existing at launch" --my version of a first run warning. Slick, ain't it. Generates a -128 when a user hits cancel

on error errorMessage number errorNumber

if errorNumber is -128 then
--this error is generated when the user hits "cancel" in the display dialog. This on error sinkholes it that so it doesn't cause any actual problems, since the end result for either Cancel or OK is what we want.

end if
end try


That’s a basic try-on error block. I could have just left the on error part blank, but just in case there’s ever a different error I want to handle differently, I’ve left a structure in place to do that. As I said, this is a big sinkhole for that error.

This next text block:

set my theSMStatusFieldText to "If you're seeing this, then there's no servers saved in the app's settings. This tab is where you add them.\r\rYou'll need three things - the server's name, URL and API Key. For the URL, only the first part, i.e. https://server.com/ is needed. The \"full\" URL is generated from that.\r\rThe app itself is pretty simple. You can add or remove servers. Those are saved locally on your mac.\rThose servers are used to pull down user info in the User Manager tab. More info is in the application help in the Help Menu."

Is used in the UI for Nagios Manager to help explain more of what’s going on. When you see the Swift code, you won’t see any of this or the swift version of the Display Dialog thing, since I haven’t actually started to build the UI yet.

else if my theSMDefaultsExist then --there's no point in running loadServerTable: if there's no data to load

my loadServerTable:(missing value) -- initial load of existing data into the server table.

end if


If the defaults exist, then we call the loadServerTable function, which loads the array controller that manages the list of servers in the UI.

Since we aren’t passing it any values, it gets a (missing value). One of the reasons for using so many properties is there are things that don’t exist in ASOC, like structs, so where you’d normally pass stuff in other languages, I find it much, MUCH easier to use properties instead.

So now, here’s my first pass at the Swift version:

//create the defautls object
var theNMDefaults = UserDefaults.standard
//the var we'll use to actually hold defaults in the app
var serverSettingsList = [String: String]()
 //register serverSettingsList as a valid defaults key
 theNMDefaults.register(defaults: serverSettingsList)
 //pull the defaults, which ends up being an array of dicts
 var theNMSettingsList = theNMDefaults.array(forKey: "serverSettingsList")
 //check to see if defaults exist at all. Even though 
 //theSMSettingsList will be nil if there's NOTHING as in no 
 //plist at all, there's also the case of the file existing, 
 //but we erased the settings or what have you. So we check 
 //the bool key we use for when the file exists but there's 
 //no settings in it. We're also doing some LIGHT variable 
 //renaming. 

//this will be false if there's nothing there or set to 
//false
 var theNMDefaultsExist = theNMDefaults.bool(forKey: "hasDefaults")

//initial if block
 if !theNMDefaultsExist {
 //display alert stating there are no defaults to read
 } else {
 //do initial loading of defaults into the appropriate 
 //controller. That will happen after we start building out 
 //the UI.
 }

So this block isn’t as complete as the ASOC version, but it’s doing the same basic stuff. Creating a user defaults object, registering with the object, checking to see if there’s any defaults, and setting up the actions if there’s anything there.

So that’s the first bit. This will be a long project, and I’m not working on it every day. (For one, it’s about impossible to test from home.)

I also plan on making all kinds of mistakes, because I’m not a full-time coder, I’m certainly not an expert in swift, and really, this app has a design target of me. So there’s things I’m doing here that someone with more of a clue wouldn’t, but meh. This app works by slinging REST commands back and forth and parsing JSON. Algorithm speed is not that essential here. I can also see that I’m going to miss a lot of the things ASOC did for me as a higher level language.

The next step will be the initial UI setup, a window with 4 tabs, and to get the initial setup working for the server manager part. So fun times.


When Bad UI Is Dangerous

CarPlay In iOS 13 has a dangerously distracting issue
(For those who will wonder, I recorded the video in this post while I was stopped.)

As some, or maybe none of you know, I am a bit of a CarPlay fan. I’ve used it since 2016, when I got my Accord EX Coupe, (I went with the EX trim specifically for CarPlay.) I like being able to use the GPS I always have with me, that I don’t need to rig up some mount for my phone, that it’s always there, and it works well. I also find that Siri in CarPlay is a vastly different and better experience than Siri anywhere else. I also love the Irish Woman voice for maps. Because Woman Speaking With Irish Accent.

However, there’s some issues with iOS 13 that I intently dislike, UI issues that should, and would be easy to fix were Apple inclined. I only bring them up because honestly, they’re distracting, and in a car, distracting is a synonym for dangerous.

Contrast Isn’t Just For Televisions Anymore

So the first faux pas, which is related to the other issue is one of contrast in the Maps destination widget, seen below:

That’s in morning sun, under a tree

As the caption states, that’s a picture of my CarPlay screen taken at about 9am under a tree. So fairly dim sunlight. That’s with the sunroof open, which it usually is. Note the lack of contrast between the close widget in the upper right corner and the rest of the widget. (I have never actually been to Peppers. It is a restaurant near the gym I go to, and CarPlay really wants me to go there. I don’t, because piss off CarPlay, I DO WHAT I WANT.)

In midday sunlight, with the sun streaming in the sun roof, that close widget effectively doesn’t exist. So to get rid of it, because if I don’t, it doesn’t let me see the map correctly in the new default view, (which I really like.) However I have to look at it to see where it is. That’s bad. What’s worse is that the tap target is just too small for something like that. No, really, here, watch:

This is entirely too fussy a tap target

Now, admittedly, that first tap kind of sucks, target-wise, but if I had been driving, that’s not going to be that unusual, especially if I’m, you know, looking at the road and not the CarPlay screen as I should be. But no matter how you look at it, that’s not good design. That’s distracting as hell, and that’s dangerous as hell. (The way I really tap is to use the other four fingers as a brace around the edge of the screen so my index finger can hit the widget more precisely. Yay big hands. I can also one-handed work the PRAM-zap combo.)

I have, in fact, reported this as a bug on 27 June. So fairly early, and it’s still open, which is not awful. But still.

This is a case where aesthetics has to take a back seat to safety. The solution is relatively easy. First, make the close target white. That makes it far, far more visible in a variety of lighting situations. Yes, that may be “ugly”, but you know what’s more ugly? Your face after an airbag explodes into it. Because you were in an accident because you were distracted. (Please spare me the “well you shouldn’t…” argument. People are imperfect, we do imperfect things. Part of design is to recognize this and help minimize it where possible. Making a small circle white instead of dark grey is a trivial change that does that.)

Secondly, and this isn’t as easy as changing the color in a widget control, but it is close, increase the size of the tap target. Allow for imperfect taps. It’s not zero-effort, but it’s hardly nuke it from orbit, it’s the only way to be sure either. It’s the kind of thing that could be handled in an update.

Talking to people with other cars and CarPlay implementations, this behavior is not universal. One person I know, their car isn’t a touch screen, so the widget I’m talking about self-dismisses after a few seconds. That’s not the case in my car, that thing will stay there until dismissed as far as I can tell. So that’s fun. Well, it’s FUn. But seriously, this is a distraction issue, and for CarPlay, those need to be high-priority bugs and fixes.

And no, I’m not going to Peppers, I DO WHAT I WANT.

Yeah, SwiftUI too dude!

SwiftUI is just not good at macOS apps

So because I’m stupid, (I know, shocker), I decided to do a thing Apple says is pretty easy: try to make a macOS app using SwiftUI. Setting aside beta issues, let me be clear on this:

  1. It’s not an iOS/iPadOS app that will run on macOS via Catalyst.
  2. It may never run on iOS/iPadOS. I don’t really care about those
  3. This is not some thing with a list picker and a nav view and hey, UI is done.

Here, a screenshot of the non-SwiftUI version:

There’s a lot going on here

So yeah. There’s a lot going on and for what it’s used for, a front end to managing my Nagios servers, it’s pretty good at it. It does thing things I need it to do. That large blank area at the bottom is for status stuff. Also, I don’t want to hear that the reason I had a hard time with this is because I haven’t been a Swift Dev since it was introduced. If the only way an environment is usable requires years of experience to do even simple things, that environment is a failure. Period.

So okay, this is pretty straightforward right? tabs, controls, done. I mean, just to mock up the UI.

Lol.

So after two weeks, spread across much longer, I can say a few things about SwiftUI.

  1. If you’re building a macOS app that is macOS – first or only, stay away from SwiftUI. Like it had the dysentery and was dragging your conestoga towards a school of piranha. It’s honestly awful, as we’ll see
  2. I’m actually really sure at this point, that SwiftUI is an attempt to bring CSS into Apple Device app UI design. In and of itself, that’s not awful. But the current implementation as of the GM 2 drop of Xcode is just awful for macOS. Again, see 1. above.

What I wanted to do was build two tabs and have some stuff in it. So let’s go through this. First, here’s the ContentView of a generic starting macOS project using SwiftUI:

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello World")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

This gets you this:

Yep. That’s a window.

So now, we want to add tabs. This requires a tab view. So we erase the text saying “Hello world” hit ye olde + button in Xcode and drag in a tab view.

This is my first complaint here: You drag a control into a code window, not onto the window of things you want. Like, I did try, and this may work correctly for i(Pad)OS apps, but for a macOS app, it’s full of fail. I tried to drag a button into a tab view, couldn’t do it. So basically, with SwiftUI, you’re not visually laying out your controls at all.

And this is what the code looks like (in the interest of space, I’m going to not include the content view preview code, it doesn’t change:

struct ContentView: View {
	var body: some View {
		TabView(selection: /*@START_MENU_TOKEN@*/ /*@PLACEHOLDER=Selection@*/.constant(1)/*@END_MENU_TOKEN@*/) {
			/*@START_MENU_TOKEN@*/Text("Tab Content 1").tabItem { Text("Tab Label 1") }.tag(1)/*@END_MENU_TOKEN@*/
			/*@START_MENU_TOKEN@*/Text("Tab Content 2").tabItem { Text("Tab Label 2") }.tag(2)/*@END_MENU_TOKEN@*/
		}
	}
}

Fugly, but it gets us this:

Okay, so far so good

Now already, this is weird. The content of the tabs occurs outside/before the tabItem itself. The label of the tab happens after the tabItem. Sort of. So let’s reformat this a bit, also so it looks more like what you see in examples:

struct ContentView: View {
	@State private var selection = 2
	var body: some View {
		TabView(selection:$selection) {
			Text("Tab Content 1")
			.tabItem{
				Text("Tab Label 1")
			}
			.tag(1)
			
			Text("Tab Content 2")
			.tabItem{
				Text("Tab Label 2")
			}
			.tag(2)
		}
	}
}

This gives us the same thing we saw before. A window with two tabs, a single string in each tab, and the tabs themselves have names. I added a line, the “@State” line, so that the first tab, with the .tag(1) property is the start tab.

But this is really weird right? First, the content, the strings “Tab Content 1” and “Tab Content 2” aren’t “in” the .tabItems. They’re before it. Whaaa? This right here is the most counterintuitive shit ever. The content of the .tabItem should be inside the .tabItem block, not outside. The name of the tab should be a property, just like the tag, i.e. .label or some such. Instead, it’s just a text string. Maybe a picture too.

So then, what if you want to put a text field with a label? Oh, then it gets stupid. So first, keeping with “content of thing before <thing>” which took me a WHILE to get, (I kept trying to put it IN the .tabItem, I mean, what kind of IDIOT would think that’s where it goes?) So now, we have this:

struct ContentView: View {
	@State private var selection = 1
	var body: some View {
		TabView(selection:$selection) {
			Text("Text Field Label")
			TextField("Text Field", text: .constant(""))
			.tabItem{
				Text("Tab Label 1")
			}
			.tag(1)
			
			Text("Tab Content 2")
			.tabItem{
				Text("Tab Label 2")
			}
			.tag(2)
		}
	}
}

Which gets us the field and the label on top of each other, because this was designed for iOS. Also the text field stretches the width of the window. It looks like this:

Sigh

Actually, that’s not what happened. SwiftUI added a third tab and now it’s all stupid. No warning. What the hell? Well, first, we can try adding a horizontal stack (read “row”) and putting things in that. Sure. let’s try that. According to Apple et al, you just cmd-click the thing you want in the HStack, select “embed in HSTack, and oh look, it also put the .tabItem in the HStack too, which was completely not what we wanted. So NOW, we put our text field inside the HStack, the next line down from the label, and put in a .frame property one line below the TextField declaration to limit the width of the textField because this is not iOS and I don’t want a gigantic text field on a 4K screen and we get closer:

struct ContentView: View {
	@State private var selection = 1
	var body: some View {
		TabView(selection:$selection) {
			HStack {
				Text("Text Field Label")
				TextField("Server URL", text: .constant(""))
					.frame(width:96,height:22)
			}
			.tabItem{
				Text("Tab Label 1")
			}
			.tag(1)
			
			Text("Tab Content 2")
			.tabItem{
				Text("Tab Label 2")
			}
			.tag(2)
		}
	}
}

which gets us:

Okay, getting there…

But we want this on the left side of our window, and at the top. So the first is pretty easy. We add a spacer, which shoves it all over to the left, and then we add in some padding so it’s not at the edge of the window. Like seriously, this is some hardcore iOS shit you have to work around. Also, why can’t I just say “hey, in this horizontal row, I would like everything to be aligned left.” That’s a great idea, so of course you can do that. LOL! Nope, in an HStack, you can only adjust the vertical alignment of the things inside it. Because that’s how everyone thinks of aligning horizontal things in a horizontal structure. Vertically.

Sigh.

So now we have:

struct ContentView: View {
	@State private var selection = 1
	var body: some View {
		TabView(selection:$selection) {
			HStack {
				Text("Text Field Label")
				TextField("Server URL", text: .constant(""))
					.frame(width:96,height:22)
			Spacer()
			}
			.padding(.leading)
			
			.tabItem{
				Text("Tab Label 1")
			}
			.tag(1)
			
			Text("Tab Content 2")
			.tabItem{
				Text("Tab Label 2")
			}
			.tag(2)
		}
	}
}

Which gives us:

Closer…

So now, what if we want two rows? Another HStack under the other one right?

Oh honey, no, it’s not that simple. That gets you a third tab. But go ahead and try it. Right, so now what? Well, now we embed this in a VStack, (Column) which will also let us handle our vertical positioning via a Spacer on that:

struct ContentView: View {
	@State private var selection = 1
	var body: some View {
		TabView(selection:$selection) {
			VStack{
				HStack {
					Text("Text Field Label 1")
					TextField("Text Field 1", text: .constant(""))
						.frame(width:96,height:22)
					Spacer()
				}
				.padding(.leading)
			Spacer()
			}
				
			.tabItem{
				Text("Tab Label 1")
			}
			.tag(1)
			
			Text("Tab Content 2")
			.tabItem{
				Text("Tab Label 2")
			}
			.tag(2)
		}
	}
}

and we get:

Closer….

Of course you’re thinking “why not just set the VStack so everything in it is at the top?” Well, that would be logical, and also have nothing to do with VStack alignment options, which are of course, left, right, and centered. Or leading/trailing/centered. Because when I’m looking at a vertical structure, I only care about horizontal alignment.

Sigh.

So now we just shove another HStack in and we’re set…sort of:

struct ContentView: View {
	@State private var selection = 1
	var body: some View {
		TabView(selection:$selection) {
			VStack{
				HStack {
					Text("Text Field Label 1")
					TextField("Text Field 1", text: .constant(""))
						.frame(width:96,height:22)
					Spacer()
				}
				.padding(.leading)
				HStack {
					Text("Label 2")
					TextField("Text Field 2", text: .constant(""))
						.frame(width:96,height:22)
					Spacer()
				}
				.padding(.leading)
				
			Spacer()
			}
				
			.tabItem{
				Text("Tab Label 1")
			}
			.tag(1)
			
			Text("Tab Content 2")
			.tabItem{
				Text("Tab Label 2")
			}
			.tag(2)
		}
	}
}

Which gets us:

Hrm…

I mean, it’s not bad, but the field alignment is kind of messed up. So how do we fix that? I mean, we could add in some padding statements, but that’s a bit much, especially as we may want to have more stuff in the window.

So to do that, we’re going to get medieval on this thing, by which I mean, we’re basically going to have to build a damned table to hold things. (Seriously, I’m having flashbacks to GoLive CyberStudio here.) Because that’s so much better than the old way.

So what we end up with are two VStacks in an HStack in a VStack. To get everything lined up correctly and with the right amount of margin from the left side of the window, we have (Comments added for clarity):

struct ContentView: View {
	@State private var selection = 1
	var body: some View {
		TabView(selection:$selection) {
			VStack {
				HStack {
					//the alignment statement here sets both labels to be left-aligned
					VStack(alignment: .leading) {
						Text("Text Field Label 1")
						Text("Label 2")
					}
					//this padding statment creates space between the edge of the
					//window and the text
					.padding(.leading)
					//this alignment property may not be strictly needed
					VStack(alignment: .leading) {
						TextField("Text Field 1", text: .constant(""))
							.frame(width:96,height:22)
						TextField("Text Field 2", text: .constant(""))
							.frame(width:96,height:22)
					}
				// this is the spacer for the HStack so it's all on the left
				Spacer()
				}
				//this is the spacer for the main VStack so it's at the top
				Spacer()
			}
			//this padding property gives us some space between the tabs and the stuff
			//in the main VStack
			.padding(.top)
				
			.tabItem{
				Text("Tab Label 1")
			}
			.tag(1)
			
			Text("Tab Content 2")
			.tabItem{
				Text("Tab Label 2")
			}
			.tag(2)
		}
	}
}

Which looks like:

Alllmost

That’s really close. but the labels and the text field alignment is kind of muffed. You might think “this is where that vertical alignment thing in the HStack comes into play!” You would think that, but you’re wrong. These are two VStacks in the HStack, remember? The way I ended up solving it was to add some padding to the bottom of the one label, so they separate out better:

struct ContentView: View {
	@State private var selection = 1
	var body: some View {
		TabView(selection:$selection) {
			VStack {
				HStack {
					//the alignment statement here sets both labels to be left-aligned
					VStack(alignment: .leading) {
						Text("Text Field Label 1")
							//this separates the labels enough so they seem to
							//line up with the text fields better
							.padding(.bottom)
						Text("Label 2")
					}
					//this padding statment creates space between the edge of the
					//window and the text
						.padding(.leading)
					//this alignment property may not be strictly needed
					VStack(alignment: .leading) {
						TextField("Text Field 1", text: .constant(""))
							.frame(width:96,height:22)
						TextField("Text Field 2", text: .constant(""))
							.frame(width:96,height:22)
					}
				// this is the spacer for the HStack so it's all on the left
				Spacer()
				}
				//this is the spacer for the main VStack so it's at the top
				Spacer()
			}
			//this padding property gives us some space between the tabs and the stuff
			//in the main VStack
			.padding(.top)
				
			.tabItem{
				Text("Tab Label 1")
			}
			.tag(1)
			
			Text("Tab Content 2")
			.tabItem{
				Text("Tab Label 2")
			}
			.tag(2)
		}
	}
}

and from this, we get:

Hey, that looks right!

Oh, so if you want your window to be the right size, you add a .frame property to the bottom of the view block:

struct ContentView: View {
	@State private var selection = 1
	var body: some View {
		TabView(selection:$selection) {
			VStack {
				HStack {
					//the alignment statement here sets both labels to be left-aligned
					VStack(alignment: .leading) {
						Text("Text Field Label 1")
							//this separates the labels enough so they seem to
							//line up with the text fields better
							.padding(.bottom)
						Text("Label 2")
					}
					//this padding statment creates space between the edge of the
					//window and the text
						.padding(.leading)
					//this alignment property may not be strictly needed
					VStack(alignment: .leading) {
						TextField("Text Field 1", text: .constant(""))
							.frame(width:96,height:22)
						TextField("Text Field 2", text: .constant(""))
							.frame(width:96,height:22)
					}
				// this is the spacer for the HStack so it's all on the left
				Spacer()
				}
				//this is the spacer for the main VStack so it's at the top
				Spacer()
			}
			//this padding property gives us some space between the tabs and the stuff
			//in the main VStack
			.padding(.top)
				
			.tabItem{
				Text("Tab Label 1")
			}
			.tag(1)
			
			Text("Tab Content 2")
			.tabItem{
				Text("Tab Label 2")
			}
			.tag(2)
		}
	//set the window size here. Beats me why
	.frame(width:706,height:651)
	}
}

And now we have a window that starts life in a precise way:

This is…this is a lot of weird-assed work to get the very simplistic results I wanted. And that’s only on one tab. I have to redo that for every tab. Literally all that code I added is to set up two labels and two text fields, that aren’t doing anything.

I freely admit that my n00bness probably caused a lot of this. But, it was not helped by nothing about using SwiftUI with macOS apps. All of Apple’s documentation and every gods-forsaken screen shot I could find were all for iOS. This may be the longest bit of anything using SwiftUI for macOS available on the internet, and that is sad. Shamefully so. Keep in mind, my end goal has a lot of UI elements. So this should be awful at some point. But I may keep banging on it, because god knows, no one else is, and even documentation from a n00b is better than nothing.

If Apple wants people to use this for macOS dev, and not just i(Pad)OS -> macOS via Catalyst, then they have to, to be blunt, pull their goddamned heads out of their asses on this. Start creating documentation and tutorials and sample code that actually works for macOS targets in and of themselves. Or just admit that SwiftUI is not a good choice for starting on macOS, and that they don’t plan to care about that. That would at least be honest.

As well, there’s some choices here that are just baffling. I refer to SwiftUI as CSS for native UI design, but even CSS isn’t as bad as some of this. Having me put all the contents for a tab before the .tabItem block and not oh, in the .tabItem block is pretty damned weird, and completely counterintuitive.

Forcing macOS devs to specify control location the way you would on i(Pad)OS is way more work than it needs to be. Let me drop a damned button on a window and give me the shell code for that.

But for now, outside of idle curiosity, I cannot see why a macOS dev would ever want to use SwiftUI, it’s just awful.

(If you feel a need to tell me what you think about this, I’m @bynkii on twitter. If you feel the need to tell me i’m stupid for not immediately falling to my knees and loving SwiftUI, you may want to not actually tell me that. It’d be a waste of time for both of us. If you want to give me some pointers from a macOS perspective, DEAR GOD PLEASE!!!!)

“It’s Complicated”

Warning: this is not about tech or D&D comedy. It’s a bit personal, and some of you may not want that here. I understand that more than you think, but it is my site, so yeah.

I have been a reader of Goldie Taylor’s work for some years now, but it wasn’t until I started following her on twitter (@goldietaylor) that I gained a true appreciation for her depth as a writer, her ability to talk about the events of her life, in particular her father, and the mystery surrounding his death, one that may never be cleared up.

The writing she’s done about her family has resonated in me in a big way, but it wasn’t until recently that it came together why: in a very real sense, I never knew my parents. I mean, they were there, my dad until 1991 when the throat cancer he’d worked so hard to get took him, and 1999 when my mom’s lungs did the same for her. I wasn’t there when either of them died. I was in the Air Force in 1991, assigned to Grand Forks, and when my mom died, I was living in Boston, she in St. Petersburg, Fl.

My mom had died some time before I knew about it. For many, that seems strange, but my mom and I would regularly go weeks, sometimes months without talking. We were…contentious…at times. All three of us. We spent a lot of time talking at, or over each other, but rarely with each other. It wasn’t until I’d tried to call her and getting no answer, called the local police to check on her that I found out I was no longer just half an orphan. The way the cop said “Mr. Welch?” told me everything he was about to say.

I was 24 when my father died, and 32 when my mother died. My father died long before my son and my somewhat disastrous first marriage happened, my mom was able to spend sometime with Alex when he was younger (two or so.) My dad was fond of telling stories about himself as a child, and as a young man, most of them…well, the movie “Big Fish” reminded me a lot of him. His stories have a kernel of truth in them, but taking any of them as gospel, probably not a great idea.

My mother said almost nothing about herself. Before she died, I knew she’d been raised in dire times (they were both born at the beginning of The Great Depression), that she had walked away from the Catholic Church after being disgusted by the opulence of a cathedral in Mexico City in 1966 (while she was pregnant with me) while so many of said cathedral’s parishioners were living in the street and starving. “They wouldn’t give them food, but still demanded tithes.” she said, with more disgust in her tone than I’ve heard before or since.

To my knowledge, she never set foot in a Catholic church for services ever again. My mother was unbending about some things.

All I knew about them as a couple was they met in the Boule Miche bar in Chicago, my dad thought she was quite attractive, they dated, they got married and well, then we were a family. They were married in a civil service in Indiana, ostensibly to avoid payback for the many pranks my dad had pulled on his siblings.

It was only later, after she died, that I found out the truth underneath a lot of things. They were married in November of 1966, I was born in March of 1967. Everyone can do that math.

Sitting on the floor of her apartment, reading that wedding license, so many things clicked. Like really why they’d gotten married in Indiana. My dad’s family was very Irish-American and very Catholic. There had always been this tension between my mother and his family. Her antipathy was barely-disguised, and after about 3-4 beers, loudly undisguised. It took me 32 years to find out why.

It took me another few years to forgive them. Which is funny, given my parents and I weren’t that close the last 10-15 years we were on the planet together, but that was us. Other people being unkind to them was not permissible. Weird, isn’t it, how that works.

What also clicked were the reasons behind the nightly, alcohol-fueled screaming matches they had. Well, alcohol-lubricated. They were fueled by anger and resentment. My mom had carved out a rather nice niche for herself in Chicago, and my surprise introduction cratered that niche, rather thoroughly. She never learned how to deal with her resentment at that, and so I was called a remarkable variety of unkind things growing up. Usually at night, with beer being the lubricant that opened the doors to where she mostly kept those feelings locked away.

I’m not angry about it anymore. Getting older does that, it gives you perspective, and with any luck, some empathy.

But when I was younger, it meant that there was this distance between us that never went away, never got better. Had they lived longer, I don’t know if it would have gotten better. I’d like to think it would have, but hard to say.

My mom had radiation poisoning from a job she had with the Air Force in Japan in the late 1940s. Her office was in Hiroshima. I found this out from a cousin long after she died. She’d come home sick, her hair falling out, weakened…then some months later, she was better and she never talked about it to my knowledge. I know she never talked about it with me. All I knew of that time was that she’d been in Japan and there are some pictures of her at parties. She is almost never smiling. There’s a wariness about her, but I don’t know why.

There are things about her life that tell me she may have not been completely straight. I can’t imagine, if that was true, what that was like for a woman in the U.S. in the 30s-60s. But it would explain much. I never knew what her life as a woman, as a person was like, and I doubt I ever will. My mother guarded her privacy more zealously than anyone I have ever known, and even if I could find out, it would almost feel like a betrayal to actively do so. Whatever reasons she had for closing off her past as tightly as she did were hers and I do respect her memory enough to allow them to remain as she preferred.

If abortion had been legal in the late 1960s, I am quite sure I’d not be here. If that would have allowed my mother to live a life with joy instead of resentment as its core, I would almost volunteer to fix that. Every unwanted kid knows that fact. No one has ever hidden that from their children.

note: This does not mean I don’t think that she loved me. I’m sure she did, I know that to be true. But love does not eradicate everything negative about a relationship.

My father was something of an open book, in that he talked a lot. What I learned, decades after his death, was that what he talked about was shallow. It wasn’t until his youngest sister sent me a box of his things that I learned things like how he’d not hit puberty until after high school. I took after him that way, fortunately my son did not. That is a shitty way to go through high school. I learned that he and his father had not gotten along well, and at least up until him being drafted into the Army for Korea, he’d not known why, but desperately wanted to fix that.

I don’t know if he really ever did, but I would like to think so.

He had a host of anger issues that he was never able to manage well. After he was mangled by a car in 1978, he spent the next 13 years kind of waiting for death. He just gave up in many ways, and the beer didn’t help. Sometimes, often times, I think the cruelest thing ever done to him by the universe was allowing him to survive that accident.

I’m regularly surprised that I’m not ardently anti-alcohol, given its centrality in so many of the worst experiences of my childhood, but ultimately, the beer just sits there. It’s the human that causes the problems.

People, not often, but sometimes ask me if I miss my parents. I say “yes” because that is what is expected. In truth, I don’t know. I miss parts of them. They were both, regularly fiercely intelligent. They were obsessive readers, and played Scrabble the way MMA fighters go after belts. They were amazing writers, and my father a just as talented photographer. I learned a lot from their good qualities, as well as their bad.

But I can’t cafeteria them. I can’t just remember parts of the people they were. For better or worse, I’m stuck with all the memories. I am specific about what I will share, but i’m stuck with the range of memories they left me with. So when someone asks me if I miss them…I don’t know. There are parts I absolutely miss, and parts I absolutely do not. Twee tropes like “don’t dwell on the bad” don’t help. They were complicated people, completely unprepared for the ADD-poster child they brought into the world. I daresay most people were unprepared for me.

They did the best they were able to do with the paucity of tools they had available. I managed to not die at a young age, and given I grew up in Miami in the 1970s/1980s, that is kind of an accomplishment.

So yeah. There’s no real point here. There may never be. Other than maybe I feel a certain sympathy/empathy with Goldie Taylor’s lack of knowledge about her father and his death.