Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Add editor buttons, changes not reflected.
-
Following this example, I wanted to move the button to a different place on the bar, between the new tab button and the wrench icon. When I add the button, if I look at the list of button items before and after, I can see that it has been added, but it's not visible. How can I make it show?
My modified code:
# coding: utf-8 from objc_util import * UIApplication = ObjCClass('UIApplication') UIBarButtonItem = ObjCClass('UIBarButtonItem') @on_main_thread def main(): global tabVC,overviewItem rootVC = UIApplication.sharedApplication().keyWindow().rootViewController() tabVC = rootVC.detailViewController() tabVC.tabCollectionView().collectionViewLayout().itemSize = CGSize(328,200) tabVC.tabCollectionView().contentInset = UIEdgeInsets(58,0,0,0) overviewItem = UIBarButtonItem.alloc().initWithImage_style_target_action_(UIImage.imageNamed_('ShowTabs'), 0, tabVC, sel('showTabOverview:')) #Add the item rightItems=list(tabVC.navigationItem().rightBarButtonItems()) print tabVC.navigationItem().rightBarButtonItems() rightItems.insert(-1,overviewItem) rightItems=ns(rightItems) rightItems.init() tabVC.navigationItem().set_rightBarButtonItems_(rightItems) print tabVC.navigationItem().rightBarButtonItems() #tabVC.persistentLeftBarButtonItems = [overviewItem] tabVC.reloadBarButtonItemsForSelectedTab() if __name__ == '__main__': main()
Mentioning people who might be able to help me:
@omz @JonB @filippocld -
Don't reload the tab.
rightItems=list(tabVC.navigationItem().rightBarButtonItems()) rightItems.insert(1,overviewItem) tabVC.navigationItem().rightBarButtonItems=rightItems
Note this does not survive tab changes... since right buttons are not persistent.
-
@JonB that doesn't work for me.
-
I have now tried adding the button in both places, as well as setting the frame. No luck. The best I can do is get spaces to be inserted between the buttons.
from objc_util import * UIApplication = ObjCClass('UIApplication') UIBarButtonItem = ObjCClass('UIBarButtonItem') rootVC = UIApplication.sharedApplication().keyWindow().rootViewController() tabVC = rootVC.detailViewController() def addButton(buttonitem): global OMbuttonitem #Add to main thing rightItems=list(tabVC.navigationItem().rightBarButtonItems()) rightItems.append(buttonitem) rightItems=ns(rightItems) tabVC.navigationItem().set_rightBarButtonItems_(rightItems) #Add to toolbar OMbuttonitem=ObjCClass('OMBarButton').alloc().initWithBarButtonItem_(buttonitem) OMbuttonitem.setFrame_(CGRect(CGPoint(832,22),CGSize(40,40))) rightItems=list(tabVC.toolbar().rightBarButtons()) rightItems.append(OMbuttonitem) rightItems=ns(rightItems) tabVC.toolbar().setRightBarButtons_(rightItems) def reset(): tabVC.reloadBarButtonItemsForSelectedTab() tabVC.toolbar().reloadRightBarButtonItems() @on_main_thread def main(): tabVC.tabCollectionView().collectionViewLayout().itemSize = CGSize(328,200) tabVC.tabCollectionView().contentInset = UIEdgeInsets(58,0,0,0) overviewItem = UIBarButtonItem.alloc().initWithImage_style_target_action_(UIImage.imageNamed_('ShowTabs'), 0, tabVC, sel('showTabOverview:')) print tabVC.navigationItem().rightBarButtonItems() print tabVC.toolbar().rightBarButtons() addButton(overviewItem) print tabVC.navigationItem().rightBarButtonItems() print tabVC.toolbar().rightBarButtons() if __name__ == '__main__': main()
It would appear that initializing the OMBarButton does not create the ImageView present in the other views.
>>> tabVC.toolbar().rightBarButtons()[0].recursiveDescription() <__NSCFString: <OMBarButton: 0x13fa71180; frame = (976 22; 40 40); layer = <CALayer: 0x13e5eefb0>> | <UIButton: 0x13faccfa0; frame = (0 0; 40 40); opaque = NO; layer = <CALayer: 0x13e5a0150>> | | <UIImageView: 0x13f8b0960; frame = (8.5 8.5; 23 23); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x13e5b8010>>> >>> tabVC.toolbar().rightBarButtons()[3].recursiveDescription() <__NSCFString: <OMBarButton: 0x13fca3f20; frame = (832 22; 40 40); layer = <CALayer: 0x13f956440>> | <UIButton: 0x13f9db140; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x13f9db3e0>>>
-
@omz suggestion: add to the documentation a description of how the Pythonista editor is structured internally.
-
Are you sure set_rightBarButtonItems is doing what you think? That is I think an undocumented method. You probably want either setRightBarButtonItems_(rightItems) or just rightBarButtonItems=rightItems. (the latter was introduced in one of the later beta versions, basically any properties with both a name() and setName_() method can be set as if the name was an attribute.
This worked for me, but realize that whenever you change a tab, the buttons get reset. I suppose it might be possible to swizzle reloadBarButtonItemsForSelectedTab to first call the original method, follwed by your special method... but this is dangerous.
Another option, which I have used before, is to just place a button as a subview to the view, flexxed to the left of the leftmost rightbutton. This requires you to make sure you manage multiple runs of the script, to ensure you don't sdd multiple copies of the button, and if making custom actions you have to manage the globals carefully, etc.
# coding: utf-8 from objc_util import * UIApplication = ObjCClass('UIApplication') UIBarButtonItem = ObjCClass('UIBarButtonItem') @on_main_thread def main(): rootVC = UIApplication.sharedApplication().keyWindow().rootViewController() tabVC = rootVC.detailViewController() tabVC.tabCollectionView().collectionViewLayout().itemSize = CGSize(328,200) tabVC.tabCollectionView().contentInset = UIEdgeInsets(58,0,0,0) overviewItem = UIBarButtonItem.alloc().initWithImage_style_target_action_(UIImage.imageNamed_('ShowTabs'), 0, tabVC, sel('showTabOverview:')) rightItems=list(tabVC.navigationItem().rightBarButtonItems()) rightItems.insert(1,overviewItem) tabVC.navigationItem().rightBarButtonItems=rightItems #tabVC.persistentLeftBarButtonItems = [overviewItem] if __name__ == '__main__': main()
-
-
I don't see how swizzling
reloadBarButtonItemsForSelectedTab
that would be "dangerous", wouldn't it go away as soon as I restart the app, or launch it "cleanly" withpythonista://
? (I'll hold off until you answer in case I'm wrong) -
As far as swizzling, how I might do it is rename the
reloadBarButtonItemsForSelectedTab
method to something else, then create a new methodreloadBarButtonItemsForSelectedTab
that calls the copied method first, then adds in my custom button. Is this possible? -
Another approach to persistence might be to look at whatever makes the persistent left button items persistent, then mimic that.
-
"undocumented method" are any documented? Where is said documentation!
-
-
For standard classes, such as start with UI, check apple docs. in this case:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UINavigationItem_Class/
Sometimes you have to look at the superclass,etc, see the class tree at the top of the page.
In general, the colons get replaced with underscores. Anything listed as a property with name can be accessed asname()
and can be set with eithersetName_()
orname=someotherthing
. For the most part, the objc_utils is smart enough to convert common types, including dicts, lists, numbers, and a few other types.For swizzling, you need to use construct an IMP (maybe a block, or a method in a custom class, I have not tried it) and use class_replaceMethod. You would need to store the pointer to the original method before you do that, or maybe something to ensure it does not get gc'd, not sure.
This might not be thr right method to swizzle, I saw @steventroughtonsmith recommended a different method. The reason it gets dangerous is you have a python method which is getting called by objc. If that python method has been cleared, you will crash. you have to take care to:
- Probably want to check whether it has already been swizzled, if so, don't reswizzle. Or at least undo the swizzle first, then reswizzle.
- The python objects need to be stored someplace where they won't be cleared by a global clearing. meaning name them with double underscores, or include them in pythonista_startup.
3). You might want a del method(or maybe better yet, a weakref.ref with a callback ) which removes the button, and unswizzles things in case you manually clear the method... I have not tried this, but should be possible in recent versions where global clearing is done within python.
-
-
I forgot that I had already written something to do this... I updated this to be a little more generic (my specific need was to be able to run a script without global clearing, which is how i like to debug)
https://github.com/jsbain/objc_hacks/blob/master/apphack.py
create_toolbar_button places a UIButton on top of the toolbar, to the left of right buttons, with an offset as specified by index. This relies on a python action function, so your example won't work exactly like this, but you could use similar ideas. I tried to make things such that they survive multiple runs or global clears. Note that if the action uses imports from custom modules not in site-packages, things may break because the modules get cleared and the function stops working. There are workarounds, that I didn't implement.