Forum Archive

[Beta] Fun with MapKit and objc_util

omz

I've been experimenting a lot with the new objc_util module lately, and I thought I'd share a few more advanced examples. So here's the first...

Please note that this relies on a couple of changes I made in the very latest build (uploaded about an hour ago), and it won't work in previous betas.

→ MapView Demo.py (Gist)

Screenshot

The example shows how you can combine the ui and objc_util modules to create entirely new UI elements, in this case a "native" MapView (which has been a popular feature request).

The MapView class should be usable pretty much like any other ui.View, but it doesn't implement all features of the underlying (fairly complex) MapKit APIs, so you might want to extend it, if you need things like satellite view etc.

Some of the code is fairly advanced, and probably requires some low-level knowledge about the Objective-C runtime to understand completely. To support being notified about scroll/zoom events, it was necessary to create a new Objective-C class that acts as the MKMapView's delegate. Creating new Objective-C classes is quite difficult to get right, and unfortunately there isn't really a good way to make this easier. The rest of the code is hopefully easier to understand – the only other "special" thing is the use of structs that aren't supported directly by objc_util (CLLocationCoordinate2D etc.). This makes it necessary to specify the return/argument types of some method calls explicitly (via the new restype/argtypes keyword arguments).

Note: I haven't tested this on a 32-bit device so far, and it's possible that some minor changes are needed to make it work on both architectures.

Feel free to ask me if there's something you don't understand in the code.

filippocld223

Nice!

Obelisk

Amazing, Mapview example.

TutorialDoctor

Great work from my favorite IOS developer. Nice indeed!

polymerchm

So if I want to dive in to the deep end of the objective-C pool, any suggestions on a good book to start with?

JonB

question re: memory management... when globals are cleared when clicking RUN, does the __del__ method get called on each deleted python object ?

I am experimenting with adding buttons to the OMTabToolBar. I was thinking one safe approach( when running my script a second time for example) would be to monkey patch del on the ui.Button to call the objective c removeFromSuperview before the object is deleted.

omz

@JonB

question re: memory management... when globals are cleared when clicking RUN, does the del method get called on each deleted python object ?

The globals are cleared using a plain old Python script. If you're curious:

import os
p = os.path.abspath(os.path.join(os.__file__, '../../clearglobals.py'))
with open(p) as f:
  print f.read()

...which is to say, the memory management should behave pretty much like everywhere else in Python.

I was thinking one safe approach( when running my script a second time for example) would be to monkey patch del on the ui.Button to call the objective c removeFromSuperview before the object is deleted.

The thing about __del__ is that implementing/monkey-patching it may actually prevent your object from ever being garbage-collected. This happens when the object is part of a reference cycle, i.e. the object directly or indirectly points to itself, which is often very hard to see because there can be multiple levels of indirection. The garbage collector is generally capable of detecting and resolving cycles (that's actually all it does in Python), but it will never free objects that are part of a cycle and implement __del__ because it cannot decide a safe order in which the objects should be freed. Those objects are added to the gc.garbage list, so you can easily check in the console if this is what's happening.

If you want to check if your custom view is already present in the toolbar, you could do this by assigning a unique tag, e.g. like this:

# ...
existing_view = toolbar.viewWithTag_(42)
if existing_view:
  existing_view.removeFromSuperview()
ObjCInstance(replacement_view).setTag_(42)
toolbar.addSubview_(replacement_view)
# ...

Of course you'd still need to ensure that your view is somehow "kept alive" as long as it's in the toolbar.

Obelisk

Memory management, del object.

JonB

omz, thanks for that. i see that weakref.finalize may be a better approach than __del__ when monkeypatching the pythonista ui (or else just hanging onto references in modules)

Webmaster4o

Can I stick a modified version of this into ui2? @omz

omz

@Webmaster4o Sure!

robertiii

So I know that this is a very old thread, however I have wanted to be able to emplement this kind of thing. I keep getting the following error on the the line

class_ptr = c.objc_allocateClassPair(NSObject.ptr, 'OMMapViewDelegate', 0)

argument 2: : wrong type

What needs to change to allow me to use this? Thanks in advance!

JonB

This probably worked in python 2.7. The string arguments need to be byte instead. The recent objc_util includes create_objc_class, which simplifies the entire class creation process, and handles strong to byte conversions.

robertiii

How do I use this and setup several different types of annotations with different images for different kinds. For instance, using a picture of a light to mark a light, a picture of a bush to mark and bush, and a picture of a tree to mark a tree.

JonB

https://gist.github.com/3fbe2e785541a43dcfcf887670feb0e6

This is an updated version of @omz's original, but converted to using create_objc_class so it works in latest version.

Also, I added a delegate method which returns an annotationView, and uses the annotation title to set the image