Developing on the cutting edge

If you’re working on a new app using the latest Xcode & macOS Monterey betas be aware of a bug that leads to SceneStorage failures appearing on the console:

Failed to restore SceneStorage with key selectedTaskId. at SwiftUI/SceneStorage.swift:105
[…]
Failed to restore SceneStorage with key sidebarSelection. at SwiftUI/SceneStorage.swift:105
Binary[2571:23261] Checked for defaults IMKUseDistributedObjects. result is 0
Binary[2571:23261] IMK will use XPC for App = com.Binary.Binary, euid=501
Binary[2571:23261] IMKInputSession [0x6000004e4a20 activate] Invoking activateServerWithReply:, from Main thread = 1, bundleID=com.apple.PressAndHold

It’s not you, it’s the Beta. Same if after updating to 12B3 you start getting EXC_BAD_ACCESS crashes, I fixed that by updating to XcodeB3.  

Screen Shot 2021 07 18 at 10 30 54 PM

Working with the latest betas is a double edged sword 🙂 

Happy Sunday, 

Marc

What Ever Happened to IBM’s Watson

Very interesting article from the NY Times on the ‘downfall’ of IBM’s Watson. 

Iu

Maps quite well with what Steve Jobs said many years ago:

“I have my own theory about why the decline happens at companies like IBM or Microsoft. The company does a great job, innovates and becomes a monopoly or close to it in some field, and then the quality of the product becomes less important. The product starts valuing the great salesmen, because they’re the ones who can move the needle on revenues, not the product engineers and designers. So the salespeople end up running the company.”

Read it here

Rich Barton: Expedia & Zillow

“How I built this” is a podcast where successful entrepreneurs are interviewed in a very candid way. The ups and downs, the complexities, the long nights… 

Today I listened to Rich Barton, the creator of Expedia, Zillow and GlassDoor. Both the show, and this specific episode are great: https://overcast.fm/+YsraNZ6eo

Iu

On a related note, here’s some of the improvement Zillow has done lately when it comes to AI: https://www.wired.com/story/zillow-taps-ai-improve-home-value-estimates/

 

Marc

Identifying combinations of dates in text using regex

For the upcoming app I’m working on I need a way to easily detect dates the user may have typed at the end of a line. It was time to re-visit old faithful regex

Ewrsit7pbw671

After lots of readings and tests I ended up settling on this beauty: 

     “.*[0-9]{2}/[0-9]{2}”

Here’s what it does:

The first section means take any character from the beginning of a line:

     .  Any character except new line

     * Zero or more charecters

provided it ends in the second part…

     [0-9]{2} Two numbers between 0 to 9

     /  This specific seperator

     [0-9]{2} Two numbers between 0 to 9

So in essence, it will take any line that ends in xx/xx where xx is any combination of two digits. That’s it, clear and simple!

***

Now, all we have to do is expand the number of cases to account for the user typing only one digit for the month or day.  

Here’s a swift array containing each case: [“.*[0-9]{2}/[0-9]{2}”,”.*[0-9]{1}/[0-9]{2}”,”.*[0-9]{2}/[0-9]{1}”,”.*[0-9]{1}/[0-9]{1}”]

One thing I’d point out is that the order of the regex pattern matters, it should go from most complex to most simple as regex will stop once it finds a suitable match. 

Here’s an example of all of it working together (in this case I’m also adding a year pattern):

func dateMatches(text: String) -> [String] {

    let regexPatternMonth = [“.*[0-9]{2}/[0-9]{2}”,“.*[0-9]{1}/[0-9]{2}”,“.*[0-9]{2}/[0-9]{1}”,“.*[0-9]{1}/[0-9]{1}”]

    let regexPatternYear = [“.*[0-9]{2}/[0-9]{2}/[0-9]{4}”,“.*[0-9]{1}/[0-9]{2}/[0-9]{4}”,“.*[0-9]{2}/[0-9]{1}/[0-9]{4}”,“.*[0-9]{1}/[0-9]{1}/[0-9]{4}”,“.*[0-9]{2}/[0-9]{2}/[0-9]{2}”,“.*[0-9]{1}/[0-9]{2}/[0-9]{2}”,“.*[0-9]{2}/[0-9]{1}/[0-9]{2}”,“.*[0-9]{1}/[0-9]{1}/[0-9]{2}”]

    

    

    let regexPatternsToMatchArray = regexPatternMonth + regexPatternYear

    do {

        let regex = try NSRegularExpression(pattern: “(\(regexPatternsToMatchArray.joined(separator:“|”)))”)

        let results = regex.matches(in: text,

                                range: NSRange(text.startIndex…, in: text))

        let finalResult = results.map {

            String(text[Range($0.range, in: text)!])

        }

        return finalResult

    } catch let error {

        print(“invalid regex: \(error.localizedDescription))

        return []

       }

}

That’s it, you can call this function and it will return the array of matching sub-strings.

From there you can process convert them into dates (make sure to account for localization as some countries use day/month and others month/day). 

Hope this helps, if you’re new to regex here are some useful references: regex cheatsheet, validatormatching valid dates and ios regex.

Comments, questions I’m at @MarcMasVi on Twitter

Marc

Struggling with a basic macOS SideBar, RowList & DetailView app in SwiftUI

Its amazing how fast you can prototype in SwiftUI, it seems like magic. At the same time, when you start to get into the details, it can be infuriating… 

Let me explain…

After completing the prototype last week and deciding on the key features for the app  I started implementing them. One of the very basic ones was having a button on the side bar to create new content. Well, basic in AppKit/UIKit, with SwiftUI on macOS not so much!

Here’s what I was trying to do:

Screen Shot 2021 06 27 at 8 04 11 PM

1. From the SideView (the column at the very left in the image above) you would be able to click on a Button that would create a new element.

2. This new element would appear as a new row (see above Added Content in the middle view) and it would become automatically selected.

3. The detail view, the one that shows the detail of the element, would automatically show all contents (see the rightmost view above).  

In AppKit this would be trivial, not so much in SwiftUI. Also, because most of the recommendations out there are for iOS and not for macOS, it was quite a fun ride. 

Here’s how I ended up doing it:

First, the SideView:

@SceneStorage(“sidebarSelection”) var selection: String?

@SceneStorage(“selectedElementId”) var selectedElementId: Int?

 

var body: some View {

    

    NavigationView{

        List(selection: $selection) {

            

            Button(action: {

                let addedElement = coreDataAddsElementHere()

                selectedElementId = Int(addedElement.id)

            }) {

                Text(“+ New Element”)

            }

            .foregroundColor(.blue)

 

            Label(“Category 1”, systemImage: “someIcon”).tag(tagThatWillBeUsedForSelection)

            Label(“Category 2”, systemImage: “someIcon”).tag(tagThatWillBeUsedForSelection2)

            Label(“Category 3”, systemImage: “someIcon”).tag(tagThatWillBeUsedForSelection3)

        }

            ElementList(typeOfList: selection ?? defaultOptionGoesHere)

    }

}

You’ll see I’m not using NavigationLink here, if you’re building a macOS app you should only use that for the RowList. To share information between the SideView and the RowList you should use SceneStorage.

Selection is used, as its name implies, to enable the selection of rows in combination with the tag property on each label: when both selection and tag match, the relevant row in the sideBar is selected.  

Second, the RowList:

@SceneStorage(“selectedElementId”) var selectedElementId: Int?

 

var body: some View {

    NavigationView{

        List(){

            ForEach(elements){element in

                NavigationLink(

                    destination: DetailView(contentsOfView: element),

                    tag: Int(element.id),

                    selection: $selectedElementId

                ) {

                    ElementListRow(title: firstLine, subtitle: otherContent, isPinned: element.pinned)

                }

                

            

                //Default View on Mac

                DetailView(contentsOfView: nil)

            }

        

        

        }

    }

}

You’ll see we capture the shared selectedElementId, which allows us to select the right row automatically (note the tag to the element id and the selection binding).

Here we can use the NavigationLink as we’re dealing with a row to detail view model. 

And finally, the detail view (optional):

@SceneStorage(“selectedElementId”) var selectedElementId: Int?

 

 

var body: some View {

 

    if selectedElementId == nil{

        Text(“Please select an element or create a new one”)

    }else{

        […]

    }

}

I use this specifically to make sure when a Core Data object is delated, if it was being shown in the detail view, I can invalidate the view. As before, I leverage the selectedElementId: if the selected element is deleted from any view, that SceneStorage id will be set to nil. The detail view detects it and immediately stops showing the cached data. 

***

And that’s it! It’s quite streightforward once you know how to do it, unfortunately most information out there is for iOS so I found myself spending way more time than anticipated. 

Hope it helps others, if you have suggestions on how to do this better please do let me know!

Do reach me @MarcMasVi on Twitter or marc.maset@hey.com

Marc

 

 

 

 

What differentiates good from great Project & Product Managers?

Although this blog is entirely focused on my developer-side, this is a part-time (nights and weekends) enterprise. My ‘main’ job is not in development, it has quite a bit to do with product management.

**I promise you this is going somewhere**

Over many years, in many roles, the one thing that I’ve seen consistently differentiate good Product & Project Managers from great ones is their ability to keep tabs on everything.

 

Great Product & Project Managers:

– Know the strategic intent of every Product Feature / Project.

– Remember the contents and agreements of every discussion.

– Timely act on any tasks they have, or follow up with others if a task is due.

– Know every single person involved and what makes them click.

– Are able to drive on many different initiatives in parallel without dropping a beat.

 

It seems magical, it seems like they have superpowers… And upon closer inspection, they do: they have a model they relentlessly follow.

 

To enable the model:

– Some use a notes app (Evernote, OneNote, Apple Notes…). 

– Others use a notebook / notes (NotePad++, vim/emacs, a real notebook…). 

– A majority uses a combination of ToDo app and a notes app (Evernote + Microsoft To Do, OneNote + Things, Apple Notes + OmniFocus…).

 

No one I’ve talked to seems to be satisfied with the apps they use. It’s not the app’s fault, they are general purpose apps after all.

So… What if we turn this around and create an app build specifically to enable great Project & Product managers?

That’s exactly what the next app I’ll be working on. And will be doing so in the open.

Screen Shot 2021 06 18 at 11 19 52 AM

More to come soon,

Marc

ML Exploration: Titanic Dataset

In summer 2019 I blogged about how I was taking a couple months to work on Machine Learning.

Since then I’ve mostly focused on software for the Mac and server-side development. My ML hands-on knowledge was getting a bit, rusty… Plus, things have evolved a bit: new technologies, new approaches, new concepts… Perfect timing as the new edition of the Hands-On ML with Sikit, Keras and TensorFlow book was recently released. 

I’ll be re-reading it and redoing all exercises. Below you’ll find the first major exercise I completed yesterday, the Titanic dataset. Today I just started a SPAM filtering model, excellent book. 

Marc

— 

import pandas as pd
import matplotlib.pyplot as plt

#—– INITIAL SETUP
titanic_train_data = pd.read_csv(‘titanicData/train.csv’)

X_train = titanic_train_data.drop(labels=‘Survived’, axis=1).copy()
y_train = titanic_train_data[[‘Survived’]].copy()

#—– DATA EXPLORATION

X_train.head()

X_train.count()
#We have a total of 891 entries. Not known for all are:
# -Cabin information is not known for all with 204 entries.
# -Age is not known for all with 714 entries.
# -Embarked is not known for all with 889 entries.

X_train.describe()
#Key insights:
# – People are quite young with median at 28 and mean at 29
# – Most people where in 2nd or 3rd class.
# – Most people did not travel with siblings or spouses SibSp, Same re. parent or children Parch.
# – Fare changes significantly and could be an indication of quality of the room.
X_train[‘Pclass’].unique()
#3 class types.

X_train[‘SibSp’].unique()
#From 1 to 8.

X_train[‘Embarked’].unique()
#S, C, Q or nan.

X_train[‘Sex’].value_counts()
#More male than female, 577 male vs 314 female.

X_train[‘Pclass’].value_counts()
#A lot more third than first, funnily enough more 1st than second.
#Plotting split between classes
plt.pie(x=X_train[‘Pclass’].value_counts(), labels=X_train[‘Pclass’].unique(),autopct=‘%1.0f%%’ )
plt.legend()
plt.show()

#Plotting where people came in the titanic
plt.bar(x=[‘S’,‘C’,‘Q’] ,height=X_train[‘Embarked’].value_counts())
plt.show()

#—– DATA PREPARATION

#Feature engineering, combine Siblings and Spouses together with Children and Parents
#X_train[‘Siblings’] = X_train[‘SibSp’] + X_train[‘Parch’]

#Remove data we won’t be using
#X_train = X_train.drop(columns=[‘PassengerId’, ‘Name’, ‘Ticket’, ‘Cabin’, ‘SibSp’, ‘Parch’])

#Test that it worked correctly
#X_train.head()
#X_train[X_train[‘Siblings’]>1]
from sklearn.base import BaseEstimator, TransformerMixin

class PrepareData(BaseEstimator, TransformerMixin):
‘Feature engineering, all custom changes are done in this class’
def __init__(self):
pass
def fit(self, X, y=None):
return self
def transform(self, X):
print(f‘About to {len(list(X))} items -> {list(X)})
X[‘Siblings’] = X[‘SibSp’] + X[‘Parch’]
print(f‘Having {len(list(X))} items -> {list(X)})
X = X.drop(columns=[‘PassengerId’, ‘Name’, ‘Ticket’, ‘Cabin’, ‘SibSp’, ‘Parch’])
print(f‘Returning {len(list(X))} items -> {list(X)})
return X

#—– PIPELINE
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder
from sklearn.compose import ColumnTransformer

num_pipeline = Pipeline(
[
(‘imputer’, SimpleImputer(strategy=‘median’)),
(‘std_scaler’, StandardScaler())
]
)

#Get the headers
X_train_num_cols = [‘Age’, ‘Siblings’, ‘Fare’, ‘Pclass’]
X_train_cat_cols = [‘Sex’, ‘Embarked’]
#Get numberical values and non numerical values
ext_pipeline = ColumnTransformer(
[
(‘num’, num_pipeline, X_train_num_cols),
(‘cat’, OneHotEncoder(handle_unknown=‘ignore’), X_train_cat_cols)
]
)

full_pipeline = Pipeline(
[
(‘custPrep’, PrepareData()),
(‘ext_pipe’, ext_pipeline)
]
)

X_train_prepared = full_pipeline.fit_transform(X_train)

#—– MODEL TRAINING AND PREDICTION USING KNEIGHBORS (TO START WITH ONE)
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score

neigh_clf = KNeighborsClassifier(n_neighbors=3, n_jobs=-1)
score = cross_val_score(neigh_clf, X_train_prepared, y=y_train.values.ravel(), cv=5)
score.mean() #80% is not bad considering 60% died and 40% survived

#Death rate
y_train.value_counts()[0]/(y_train.value_counts()[0]+y_train.value_counts()[1])
#—– IMPROVE MODEL THROUGH GRID-SEARCH
from sklearn.model_selection import GridSearchCV

param_grid = [
{
‘n_neighbors’:[3, 15, 30, 40, 50],
‘leaf_size’: [15, 20, 30, 35, 45],
‘weights’: [‘uniform’, ‘distance’]
}
]

neigh_clf = KNeighborsClassifier()
grid_search = GridSearchCV(neigh_clf, param_grid, cv=3, return_train_score=True)
grid_search.fit(X_train_prepared, y_train.values.ravel())
grid_search.best_params_
grid_search.best_score_
#{‘leaf_size’: 15, ‘n_neighbors’: 30, ‘weights’: ‘uniform’}
#—– PREPARING FOR SUMBISSION WITH IMPROVED MODEL
neigh_clf = grid_search.best_estimator_
neigh_clf.fit(X_train_prepared, y_train.values.ravel())

X_test = pd.read_csv(‘titanicData/test.csv’)
#y_test_withId = pd.read_csv(‘titanicData/gender_submission.csv’)
#y_test = y_test_withId.drop(columns=[‘PassengerId’])

X_test_prepared = full_pipeline.transform(X_test)

from sklearn.metrics import accuracy_score
y_test_pred = neigh_clf.predict(X_test_prepared)
#accuracy_score(y_test, y_test_pred) Can’t use as y_test data is fake. Need to submit to kaggle to get the right data

#—– USE SVM (TO TRY ANOTHER MODEL)

from sklearn import svm

svm_clf = svm.SVC(kernel= ‘poly’)
svm_clf.fit(X_train_prepared, y_train.values.ravel())
y_test_pred = svm_clf.predict(X_test_prepared)
#accuracy_score(y_test, y_test_pred)

#Lets try with linear kernel
svm_clf = svm.SVC(kernel= ‘linear’)
svm_clf.fit(X_train_prepared, y_train.values.ravel())
y_test_pred = svm_clf.predict(X_test_prepared)
#accuracy_score(y_test, y_test_pred)
#We can find as well coeficiants of feature importance
svm_clf.coef_[0]

#And confusion matrix
from sklearn.metrics import plot_confusion_matrix
plot_confusion_matrix(svm_clf, X_test_prepared, y_test.values.ravel(),
cmap=plt.cm.Blues)

#And directly calculating numbers and graphing it in a diferent way
from sklearn.metrics import confusion_matrix
#conf_mx = confusion_matrix(y_test, y_test_pred)
#plt.matshow(conf_mx, cmap=plt.cm.gray)

#—– GET READY FOR SUBMISSION TO KAGGLE
y_test_withId = pd.read_csv(‘titanicData/gender_submission.csv’)
y_test_withId[‘Survived’] = y_test_pred
y_test_withId.to_csv(‘submission.csv’, index=False)

NewsWave 2021.5 for Mac & iOS

I’m happy to report that NewsWave 2021.5 for Mac & iOS has been submitted to the App Store.This is a minor update for both apps, focusing on improving stability and addressing minor edge case bugs. 

This includes better handling of posts returning ‘NULL’ as the summary or edge case handling for certain websites, like ‘Engadget’, returning “'” instead of an apostrophe. 

If you have any comments or feedback do reach me @MarcMasVi on Twitter or marc.maset@hey.com

Hope you enjoy the update,

Marc