Skip to content

Instantly share code, notes, and snippets.

@jsbain
Created December 17, 2017 06:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jsbain/874c3aead15a8be3c847f5c46c6397db to your computer and use it in GitHub Desktop.
Save jsbain/874c3aead15a8be3c847f5c46c6397db to your computer and use it in GitHub Desktop.
objc_classes.py
from objc_util import *
import weakref
''' Attempt at making more natural objc class in pythonista.
decorate class wth @objc_class.
set superclass and protocols if desired.
decorate objc methods with @objc_method
e.g
@objcclass
class MySearchResultUpdater(object):
protocol=['UISearchControllerUpdating']
superclass=NSObject
def __init_(self):
self.tv=None
@objcmethod
def updateSearchResultsForSearchController_(_self,_sel, controller):
self=MySearchResultUpdater(_self) # access self from callback
tv=self.tv #instance variable!
if ObjCInstance(controller).active():
sb=ObjCInstance(controller).searchBar()
filterTerm=str(sb.text())
tv.data_source.filter_items(filterTerm)
else:
tv.data_source.filter_items('')
you can access self by using YourClassName(_self), then get to py instance vars, methods, etc
The way this works is that the objc class is created, and attached to the py class.
'''
def objcmethod(fcn):
'''fcn gets wrapped as static., and marked so we can find it.
'''
fcn._objcmethod=True
return staticmethod(fcn)
def get_objc_methods(cls):
'''find all tagged methods. note, this only works with protocol'''
methods=[]
for m in cls.__dict__.values():
try:
if m.__func__._objcmethod:
methods.append(m.__func__)
except AttributeError:
pass
return methods
def objcclass(cls):
'''decorator which creates an objcclass'''
methods=get_objc_methods(cls)
if hasattr(cls,'superclass'):
superclass=cls.superclass
else:
superclass=ObjCClass('NSObject')
if hasattr(cls,'protocols'):
protocols=cls.protocols
else:
protocols=[]
cls._objcclass=create_objc_class(cls.__name__,
superclass=superclass,
methods=methods,
protocols=protocols, debug=True)
oldinit=cls.__init__
cls.instances={}
def __init__(self,*args,**kwargs):
oldinit(self, *args, **kwargs)
self.objcinstance=cls._objcclass.new()
self._objc_ptr=self.objcinstance.ptr
cls.instances[self.objcinstance.ptr]=weakref.ref(self)
def get_instance(*args):
if args:
return cls.instances[args[0]]()
else:
return cls()
cls.__init__=__init__
return get_instance
def ObjCBlockWrapper(argtypes=[c_void_p], restype=None):
def wrapper(func):
return ObjCBlock(func,argtypes=argtypes,restype=restype)
return wrapper
'''
@objcclass
class JBQLDatasource(object):
protocols=['QLPreviewControllerDataSource',
'QLPreviewControllerDelegate']
def __init__(self):
self.items=[] #list of NSURLs
@objcmethod
def numberOfPreviewItemsInPreviewController_(_self,_sel,pvc):
print('called')
return len(JBQLDatasource(_self).items)
@objcmethod
def previewController_previewItemAtIndex_( _self, _sel, controller, index):
self=JBQLDatasource(_self)
return self.items[index].ptr
j=JBQLDatasource()
import os
for f in os.listdir('.'):
if '.py' not in f:
j.items.append(nsurl(os.path.abspath(f)))
pyk=ObjCClass("PYKConsoleQuickLookController").new()
pyk.setCurrentItems=ns(j.items)
pvc=QLPreviewController.new()
pvc.dataSource=j.objcinstance
import ui
v=ui.View(frame=(0,0,400,400))
v.content_mode=ui.CONTENT_SCALE_ASPECT_FIT
ObjCInstance(v).addSubview_(pvc.view())
pvc.view.frame=ObjCInstance(v).bounds
v.present('sheet')
'''
def ObjCBlockWrapper(argtypes=c_void_p, restype=None):
def deco(func):
def wrapper(*args,**kwargs):
return func(*args,**kwargs)
def blk(*args,**kwargs):
ObjCBlock(wrapper,restype,argtypes).__call__
#return wrapper
return deco
import ui
from ctypes import py_object
from objc_util import *
import sys
from objc_classes import objcmethod, objcclass
UISearchController=ObjCClass('UISearchController')
class SearchableTableView(ui.View):
'''Works like a tableview, except that search bar is shown.
TODO: Extra search_delegate attrib, exposes:
update_search_results(tv, searchbar)
scope_button_titles(tv)
'''
def __init__(self,*args,**kwargs):
#setup a tableview
ui.View.__init__(self,*args,**kwargs)
tv=ui.TableView(name='tv1',*args,**kwargs)
self.bounds=tv.frame
tv.flex='wh'
self.add_subview(tv)
searchController=UISearchController.alloc().initWithSearchResultsController_(None)
updater = MySearchResultUpdater()
searchController.searchResultsUpdater = updater
self.updater=updater
updater.tv=self
searchController.dimsBackgroundDuringPresentation = False #True prevents selection
searchController.definesPresentationContext = False
searchController.hidesNavigationBarDuringPresentation=False #don't change this'
#searchController.searchBar().scopeButtonTitles=ns(['A','B']) # this doesnt work..
#searchController.searchBar().sizeToFit()
self.tv=tv
self.searchController = searchController
ObjCInstance(self.tv).tableHeaderView = searchController.searchBar()
def __getattribute__(self,attr):
'''Mock most attributes to tableview, except for the ones listed'''
attrlist=['left_button_items', 'y', 'flex', 'touch_enabled', 'superview', 'x', 'transform', 'navigation_view', 'present', 'on_screen', 'close', 'bring_to_front', 'frame', 'center', 'width', 'send_to_back', 'add_subview', 'name', 'autoresizing', 'subviews', 'bounds', 'wait_modal', 'remove_subview', 'right_button_items','tv']
if not (attr.startswith('_') or attr in attrlist) and hasattr(self.tv,attr):
return getattr(self.tv, attr)
else:
return object.__getattribute__(self,attr)
def __setattr__(self,attr,value):
attrlist=['left_button_items', 'y', 'flex', 'touch_enabled', 'superview', 'x', 'transform', 'navigation_view', 'present', 'on_screen', 'close', 'bring_to_front', 'frame', 'center', 'width', 'send_to_back', 'add_subview', 'name', 'autoresizing', 'subviews', 'bounds', 'wait_modal', 'remove_subview', 'right_button_items','tv']
if not (attr.startswith('_') or attr in attrlist) and hasattr(self,'tv') and hasattr(self.tv,attr):
setattr(self.tv, attr,value)
else:
object.__setattr__(self,attr,value)
@objcclass
class MySearchResultUpdater(object):
'''TODO: implement this as a standard delegate, and have a python search_delegate'''
protocol=['UISearchControllerUpdating']
def __init_(self):
self.tv=None
@objcmethod
def updateSearchResultsForSearchController_(_self,_sel, controller):
tv=MySearchResultUpdater(_self).tv
if ObjCInstance(controller).active():
sb=ObjCInstance(controller).searchBar()
filterTerm=str(sb.text())
tv.data_source.filter_items(filterTerm)
else:
tv.data_source.filter_items('')
class FilteredListDataSource(ui.ListDataSource):
'''TOdo: deleting and moving items does not work. '''
def __init__(self, items=None, filter=''):
ui.ListDataSource.__init__(self,items)
self._allitems=list(self._items)
def filter_items(self,filt):
if not filt:
self._items=list(self._allitems)
else:
self._items=[item for item in list(self._allitems) if filt in item]
self.reload()
@property
def items(self):
return self._items
@items.setter
def items(self, value):
self._allitems = ui.ListDataSourceList(value, self)
self._items=self._allitems
self.reload()
if __name__=='__main__':
L=FilteredListDataSource(['hello','world','this','is','a','test'])
stv=SearchableTableView(frame=(0,0,200,700))
stv.data_source=stv.delegate=L
stv.present('panel')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment