Programmieren

Meine erste eigene iPhone (Location Sharing) App

Der Vorsatz eine eigene App im Apple Store zu haben besteht schon lange, doch irgendwie hat es bis jetzt nie geklappt. Und das obwohl ich schon 6 Jahre für die iOS Plattform programmiere. Nachdem ich inzwischen sogar selbständiger iOS Entwickler bin, konnte ich diesen Umstand so nicht belassen. Es blieb die Frage was die eigene App eigentlich machen bzw. welches Problem sie für mich, aber vor allem die Nutzer, lösen soll. Die kurze Antwort darauf ist: Location Sharing. Der primäre Grund dafür ist, dass ich mit den vorhandenen Lösungen unzufrieden war. Entweder waren diese zu umständlich, die Nutzeroberfläche nicht durchdacht, der Dienst zu langsam, ungenau oder unzuverlässig. Damit waren auch schon die Ziele der neuen App definiert: Einfach, Schnell, Präzise, Zuverlässig und da es hier um Positionsdaten von Nutzern geht, mit höchster Priorität auch Sicherheit.

Was ist dieses "Location Sharing" und wozu brauche ich es?

Mit dem Begriff "Location Sharing" wird die Möglichkeit beschrieben, den eigenen Standort mit einer anderen Person zu teilen. In der Regel wird dieser Person einfach eine Nachricht mit einem Link zu einer Webseite gesendet, über die der eigene Standort auf einer Karte angezeigt werden kann. Der Andere weiß somit wann ich mich wo befinde. Doch wozu nutze ich diese Funktion?

  • Jemanden wissen zu lassen, dass ich mich gerade auf dem Weg befinde. Egal ob der ich von der Arbeit nach Hause fahre, oder mich mit Freunden treffe.
  • Treffen ohne genauen Treff- und Zeitpunkt, wie zum Beispiel beim Shopping, in einem Park oder einem Konzert.
  • Den richtigen Weg finden. Wenn man in einer unbekannten Umgebung nicht weiter weiß, kann man einfach den Standort teilen und sich von einem Bekannten den Weg erklären lassen.

Es gibt aber viele weitere Situationen, in denen ich inzwischen gerne meinen Standort teile.

Aktueller Stand

Seit der Idee und Konzeption ist nun schon einige Zeit vergangen. Da es sich bei einem solchen Dienst nicht "einfach" nur um eine App handelt, sondern auch um einen Webservice, eine Webseite und ein paar weitere Dinge, war auch wirklich viel zu tun. Das meiste ist nun aber geschafft und ein Ende der Entwicklung in Sicht. Mit dem Ergebnis bin ich bisher sehr zufrieden, seit wenigen Monaten läuft bereits ein geschlossener Betatest des Dienstes. Entsprechend aktueller Planung wird die App im ersten Halbjahr 2017 im App Store veröffentlicht. Mehr möchte ich jetzt hier aber noch nicht verraten.

Betatester gesucht

Um die Zuverlässig- und Skalierbarkeit der App besser beurteilen zu können, benötige ich aktuell noch ein paar Betatester. Wer andere Dienste, wie das in iOS integrierte Teilen des Standortes oder Apps wie Glympse regelmäßig nutzt und daran interessiert ist meine App zu Testen, kann sich über das folgende Formular zum Betatest anmelden. Wichtig ist, dass die App nur für das iPhone gemacht ist, Nutzer eines iPads oder anderer Smartphones können diese somit aktuell nicht installieren. Nach erfolgreicher Anmeldung sende ich Dir eine TestFlight Einladung zur App, diese enthält eine Anleitung wie Du die App auf Deinem iPhone installieren kannst. Zusätzlich erhältst Du eine Einladung zu einem Slack Channel, den ich für die Kommunikation mit den Testern nutze. Dort kann man mich am einfachsten direkt erreichen, ob mit Fragen, Ideen oder Problemen.

Custom compass position in MKMapView

For map views in native iOS apps MKMapView is my first choice. While implementing an map based app I came to the point where the compass was showing under a menu component. This is because the default compass position is in the top right corner of the map view. So there were two solutions to the problem:

  1. Change the position of the compass
  2. Remove the compass and implement a custom one

Although the second option sounded tempting, the first one seemed to be the obvious choice. But then I learned that the MKMapView doesn't let you do anything with its compass besides the possibility to show or hide it via the 'showCompass' function. I was surprised by this fact, because on the iOS default map app you can see the compass on a custom position.

Custom compass position in apple maps app
Custom compass position in apple maps app

Solving the puzzle

By analyzing the issue I saw the compass is a subview of the map with a custom class called 'MKCompassView'. As the class itself is private API it is not exposed for external use, but I don't need that. To set a new position for the compass I subclassed the MKMapView component and overrode the layoutSubviews function to change the frame of the MKCompassView.

As you can see this is really easy. Then I just had tell the app to use the map view subclass as the map component and my problem was solved.

 
App showing the compass at a custom position on a MKMapView
App showing the compass at a custom position on a MKMapView
 

Make it useful

Then I realized that a static position isn't good at all in my case, you will see the reason later. So I made the position variable and added a simple animation. The final code looks like this:

Inside the app it looks like this:

 
Animated custom compass position on a MKMapView
Animated custom compass position on a MKMapView
 

E-Mail-Bilder mit OCR auslesen - Harvester lernen lesen

Die Impressumspflicht zwingt quasi jeden Betreiber einer Webseite ein Impressum einzubinden. Hier müssen einige persönliche Informationen angegeben werden, was im Internet auch schnell mal missbraucht wird. So ist schon lange bekannt, dass sogenannte Harvester durch einfaches parsen von Webseiten unter anderem E-Mail-Adressen sammeln, die dann für den Spamversand genutzt werden. Aus diesem Grund wurden schon viele Methoden entwickelt, um das einfache Auslesen der E-Mail-Adresse zu verhindern. Einige basieren auf der Manipulation der Anzeige über CSS oder Javascript, da die Harvester, zumindest bis heute, nur den html-Quelltext der Webseiten durchsuchen ohne Javascript und CSS zu interpretieren. Einen interessantes Projekt in diesem Zusammenhang findet man hier: http://www.drweb.de/magazin/wirklich-wirksamer-schutz-fr-e-mail-adressen/

Doch in diesem Artikel möchte ich mich mit einem anderen Schutzmechanismus beschäftigen. Mit der Verwendung von Bildern, auf denen die E-Mail-Adresse geschrieben steht, sollen Harvester ausgetrickst werden, da sie den Text nicht lesen können. Oder etwa doch?

Da ich in mehreren Projekten bereits mit OCR (Abkürzung für: Optical Charater Recognition) in Berührung gekommen bin, also dem computergestützten Erkennen von Schrift in Bilddateien, stellte ich mir die Frage wie sicher diese E-Mail-Bilder wirklich sind. Einem entsprechend programmierten Harvester sollte es doch möglich sein den Text auf den Bilddateien zu erkennen und somit die E-Mail-Adresse zu ermitteln.

Aus der Neugier heraus, ob das wirklich funktioniert, habe ich hierfür ein Testprogramm als Proof-Of-Concept geschrieben. Die Basis für die Erkennung sind 30 selbst erstellte E-Mail-Bilder, die ich mittels verschiedener Onlinedienste erzeugt habe. Um ein möglichst realistätsnahes Ergebnis zu erhalten, habe ich außerdem mehrere übliche Variationen beim Erzeugen angewendet.

 service_1_0

service_1_0

 service_1_1

service_1_1

 service_1_2

service_1_2

 service_2_0_0

service_2_0_0

 service_2_0_1

service_2_0_1

 service_2_0_2

service_2_0_2

 service_2_1_0

service_2_1_0

 service_2_1_1

service_2_1_1

 service_2_1_2

service_2_1_2

 service_2_2_0

service_2_2_0

 service_2_2_1

service_2_2_1

 service_2_2_2

service_2_2_2

 service_3_0

service_3_0

 service_3_1

service_3_1

 service_3_2

service_3_2

 service_4_0

service_4_0

 service_4_1

service_4_1

 service_4_2

service_4_2

 service_5_0

service_5_0

 service_5_1

service_5_1

 service_5_2

service_5_2

 service_6_0_0

service_6_0_0

 service_6_0_1

service_6_0_1

 service_6_0_2

service_6_0_2

 service_6_1_0

service_6_1_0

 service_6_1_1

service_6_1_1

 service_6_1_2

service_6_1_2

 service_7_0

service_7_0

 service_7_1

service_7_1

 service_7_2

service_7_2

Der Programmablauf ist dabei wie folgt:

  1. Optimieren der Bilder für die OCR - Da eine OCR meist für die Erkennung von Text auf gescannten Dokumenten optimiert ist, hat diese bestimmte Anforderungen an das Quellmaterial. Die hier verwendeten E-Mail-Bilder wurden deshalb vor der Texterkennung optimiert, um bessere Ergebnisse zu erhalten. Nach vielen Versuchen bin ich bei folgendem Algorithmus angekommen: Bild in Graustufen umzuwandeln, skalieren und dann den Kontrast erhöhen. Ein interessanter Effekt hierbei ist, dass sich dadurch nicht nur die Erkennungsqoute gegenüber dem unbearbeiteten Bild verbessert hat, sondern auch die Verarbeitungsgeschwindigkeit der OCR deutlich erhöht wurde.
  2. Auslesen der E-Mail-Adresse aus den Bildern - Da ich es auch schon in anderen Projekten gemacht hatte, lag es nahe die freie OCR-Engine "tesseract" zu verwenden¹. Sie liefert ausreichend gute Ergebnisse, auch wenn die proprietäre Konkurrenz bekanntermaßen bessere Erkennungsraten bietet.

Zum Schluß habe ich das Ergebnis noch manuell kontrolliert, um zu prüfen ob die OCR richtig lag.

Lets go

 Programmdurchlauf des EMailImageReader

Programmdurchlauf des EMailImageReader

Nun sollen endlich ein paar Zahlen zu den Ergebnissen folgen, wobei ich mir natürlich bewusst bin, dass die verwendeten Inputbilder keine repräsentative Stichprobe, aber für diesen Test absolut ausreichend sind. Die Verarbeitungszeit der 30 Bilder betrug über mehrere Läufe gemittelt etwa 4,5 Sekunden. Die Erkennungsquote der OCR lag dabei im Durchschnitt bei 33 %. Wenn genügend Inputbilder zur Verfügung stehen ergeben sich daraus 133 korrekt ermittelte E-Mail-Adressen pro Minute, was hochgerechnet fast 8000 E-Mail-Adressen pro Stunde ergibt. Auch wenn klassische Harvester vermutlich eine deutlich größere Leistung durch einfaches Parsen von Webseiten erreichen, ist dies ein beachtlicher Wert.

Wie erwartet war die Erkennung bei Bildern mit sehr speziellen Fonts oder Bildrauschen sehr schlecht. Man muss aber auch anmerken, dass das hier verwendete Programm nur als Proof-Of-Concept bezeichnet werden kann. Es gibt noch einige Möglichkeiten die Erkennungsquote und Performance deutlich zu erhöhen und damit die Anzahl korrekt erkannter E-Mail-Adressen pro Minute.

  • Der oben beschriebene Algorithmus zur Bildoptimierung lässt sich zum Beispiel um das Entfernen von Bildrauschen oder eine dynamische Kontrastverbesserung erweitern.
  • Die einzelnen Anwendungsschritte lassen sich gut parallelisieren. Durch den Einsatz von Multithreading kann das Suchen und Laden der Bilddateien, die Optimierung der Bilddateien und die OCR gleichzeitig für verschiedene E-Mail-Bilder erfolgen. Die Geschwindigkeit dürfte sich dadurch leicht um den Faktor 5 - 10 erhöhen lassen und ist dann hauptsächlich durch die Systemleistung (CPU für OCR und Internetanbindung wegen Webseiten- und Bilder-Download) beschränkt.
  • Durch Optimierung der OCR lässt sich die Erkennungsrate noch deutlich erhöhen. Zum Beispiel ließe sich die OCR trainieren, so dass E-Mail-Adressen besser erkannt werden. Alternativ kann auch eine sogenannte Whitelist angegeben werden, die nur Zeichen enthält, die auch in E-Mail-Adressen zulässig sind. Über ein Wörterbuch, dass die häufigsten E-Mail-Provider-Domains enthält, kann die OCR-Erkennung nochmals deutlich verbessert werden. Man könnte das Wörterbuch sogar beim Crawlen dynamisch um die Domainnamen erweitern, von welchen entsprechende Bildern geladen wurden.

Wie wahrscheinlich ist der Einsatz von OCR bei Harvestern?

Auch wenn es keine technischen Hürden gibt mittels dem hier beschriebenen Verfahren E-Mail-Adressen zu sammeln, gibt es keinen Grund zur Panik. Es ist doch sehr unwahrscheinlich, dass diese Technik von Harvestern verwendet wird, auch in absehbarer Zukunft. Folgende Punkte müssen nämlich bedacht werden:

  • Da die OCR-Technologie relativ viel CPU-Leistung benötigt, ist das hier beschriebenene Verfahren deutlich rechen- und somit kostenintesiver als das herkömmliche Parsen von Webseiten.
  • Damit ein Crawler möglichst effizient arbeitet, müsste er für die vielen verschiedenen Anbieter und Varianten von E-Mail-Bildern speziell angepasst werden, was einen hohen Aufwand verursacht.
  • Manche Anbieter von E-Mail-Bildern bieten die Möglichkeit die Providerdomain als separate Grafik abzubilden (z.B.: @gmail.com) und Textfarben und -größen zu ändern um OCR zur erschwehren. Diese Techniken bieten zwar keinen echten Schutz, empfehle ich aber trotzdem jedem, der auf E-Mail-Bilder nicht verzichten kann oder möchte. Der Aufwand dem Crawler das Erkennen der Grafiken "beizubringen" dürfte auch kaum im Verhältnis zum daraus resultierenden Nutzen stehen.
  • Tendenziell werden Personen, die Ihre E-Mail-Adresse mit so viel Aufwand versuchen zu schützen, kaum auf den versendeten Spam "klicken". Die Opferquote dürfte hier viel geringer sein, als unter Personen, deren E-Mail-Adressen unbedarft ins Internet gelangen. Eine teuere Optimierung durchzuführen um danach nicht die primäre Zielgruppe zu erreichen

E-Mail-Bilder sicher nutzen

Auch wenn man vor Lesenden-Harvestern aus oben genannten Gründen keine Angst haben muss, lässt sich der Einsatz von E-Mail-Bildern mit ein paar einfachen Richtlinien weiter absichern:

  • Binden Sie das Bild nicht über einen Link zum Hersteller ein, da dieser über das Parsen der Seite Rückschlüsse zulässt. Legen Sie das Bild einfach auf dem eigenen Webspace ab.
  • Wenn das Bild über JavaScript / CSS eingebunden wird, kann der Link zum Bild in der Regel nicht durch einfaches Parsen der Webseite ermittelt werden.
  • Verwenden Sie keinen Dateinamen, der darauf hinweist, dass auf dem Bild eine E-Mail-Adresse vorhanden ist.

¹ Es ist zwar möglich eine in .NET verwendbare Version von tesseract aus dem Quelltext zu erzeugen allerdings gibt es hierfür auch schon fertige Projekte im Netz, die das Einbinden der OCR deutlich vereinfachen. Ich habe in diesem Fall das Projekt von Charles Weld verwendet: .NET Wrapper für tesseract (GitHub-Link)

Ribbon XML in Microsoft Office Addins

Der folgende Artikel befasst sich mit der Erstellung von Oberflächenelementen in Microsoft Office Addins, welche Probleme autreten können und wie man Sie lösen kann. Die Code-Snippets und das am Ende des Posts bereitgestellte Beispielprojekt beziehen sich auf ein Outlook-Addin, die verwendeten Techniken lassen sich aber auch direkt auf Word- oder Excel-Addins anwenden.

Designer vs. XML

Wenn man mit Visual Studio eine GUI für ein Microsoft Office Addin erstellen möchte hat man die Wahl, ob man mit dem grafischen Designer oder direkt im XML-Editor arbeiten möchte. Sicherlich ist die erste Variante einfacher, beschränkt sich aber auf das Erstellen von Ribbons. Manche Anpassungen der GUI, wie zum Beispiel das Kontextmenü, können somit nur per XML-Datei manipuliert werden. Leider lassen sich die unterschiedlichen Methoden nicht mischen¹, man muss in komplexeren Szenarien durchgängig die Oberfläche in XML beschreiben.

Neben dem geringeren Komfort für den Entwickler gibt es aber noch weitere Schattenseiten. Besonders dynamische Oberflächen, die Lokalisierung verwenden oder Elemente bedingt ausblenden, sind hiermit recht umständlich abzubilden. Der größte Nachteil kommt aber zum tragen, wenn man verschiedene Oberflächenelemente, wie Ribbon und Kontextmenu, unabhängig voneinander bei unterschiedlichen RiddonIDs verwenden möchte. Hierzu muss man nämlich für jede Kombination der Elemente eine eigene XML-Datei erstellen. Damit sinkt wiederum die Wartbarkeit und Wiederverwendbarkeit des Quellcodes. Doch dieses Problem lässt sich beheben. Warum beschreibt man nicht einfach jedes Oberflächenelement in einer XML-Datei und fügt sie dann im Programmcode zur konkreten Oberfläche zusammen?

Funktionsweise von Ribbon XML

Zum besseren Verständnis der ganzen Lösung beschreibe ich nochmal kurz die Funktionsweise von Ribbon XML. Wenn man dem AddIn-Projekt ein Ribbon XML Element hinzufügt wird nicht nur eine XML-Datei, sondern auch eine zugehörige Klasse erzeugt. Der Hook für die Ausführung von Ribbon XML ist die Funktion "CreateRibbonExtensibilityObject()" in der ThisAddin-Klasse.

Hiermit wird beim Laden der grafischen Oberfläche die Funktion "GetCustomUI" in der Klasse "CustomUIManager" aufgerufen und bietet damit die Möglichkeit eigene Oberflächenelemente zu erstellen. Wenn man mehrere getrennte Oberflächenelemente erzeugen können möchte und damit mehrere XML-Dateien verwendet, muss eine der automatisch erzeugten Klassen als Controller verwenden, die dann die Oberflächenerstellung übernimmt. Genau dort steckt auch der Kern der ganzen Lösung. Abhängig von der an die Funktion "GetCustomUI" übergebenen RibbonID, wird das entsprechende RibbonXML-String erzeugt und zurückgegeben. Die RibbonID ist ein String der angibt welcher Teil der Anwendungsoberfläche gerade geladen wird. Eine vollständige Liste der RibbonIDs in Microsoft Outlook kann im MSDN nachgelesen werden. Die restlichen Office-Anwendungen besitzen keine so komplexe Unterteilung der Oberfläche und verwenden jeweils nur eine RibbonID (im Einzelnen Microsoft.Word.Document, Microsoft.Excel.Workbook und Microsoft.PowerPoint.Presentation).

Auf den ersten Blick mag es vielleicht etwas unübersichtlich aussehen, ist aber ganz einfach. Jedes mal wenn die Oberfläche geladen wird, z.B. beim Anwendungsstart oder durch Interaktion des Nutzers, wird die Funktion "GetCustomUI" aufgerufen. In dieser wird dann die XML-Repräsenation der eigenen Oberflächenelemente erzeugt und zurückgegeben. Die einzelnen Schritte sind:

  1. Prüfen ob für die übergebene RibbonID eine eigene Oberfläche erzeugt werden soll.
    • ja: weiter mit den folgenden Schritten 2 bis 4
    • nein: sofort zurückkehren um die Ausführung nicht zu blockieren
  2. Lokalisieren der erzeugten Oberfläche mit der Funktion "LocalizeRibbonXML". Hierfür werden lediglich alle Einträge einer Ressourcendatei geladen um die Texte der Oberfläche zu setzen.
  3. Bedingtes Ausblenden von Elementen mit der Funktion "HideRibbonXMLElement". Hiermit könnnen einzelne Oberflächenelemente, falls sie aus bestimmten Gründen nicht angezeigt werden sollen, ausgeblendet werden.
  4. Zurückgeben des erzeugten XML-Strings.

Außerdem habe ich noch die Funktion "OnRibbonElement_Click" für das Eventhandling hinzugefügt um Nutzereingaben entsprechend behandeln zu können.

Das Vorgehen setzt natürlich vorraus, dass die verwendeten XML-Dateien keine XML-Header und CustomUI-Tags enthalten, der Inhalt der Datei "OUI_ContextMenu.xml" sieht bei mir zum Beispiel so aus:

Zum Ausprobieren, Weiterentwicklen und Wiederverwenden habe ich mein vollständiges Visual Studio 2010-DemoProjekt für ein Outlook 2010-AddIn zum Download angehängt. Das Konzept wurde ebenfalls mit Visual Studio 2012 und Office 2013 getestet, kann also auch in neueren Projekten angewendet werden.

1 Es ist nicht ganz richtig, dass sich Designer- und XML-Methode nicht in einem Projekt gemeinsam verwenden lassen. Wenn man die Funktion "CreateRibbonExtensibilityObject()" in der ThisAddin-Klasse, die für die Verwendung der XML-Methode benötigt wird.