omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    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.


    Old methods to grab console text no longer work in Pythonista 3.3?

    Pythonista
    console text
    4
    9
    3257
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • shinyformica
      shinyformica last edited by

      So I used to be able to do one of these two tricks to find the console text view in Pythonista 3.2 and grab the text out of it...

      Search the view hierarchy from the root and compare objc classes:

      def findConsoleView():
          OMTextView = objc_util.ObjCClass("OMTextView")
          OMTextEditorView = objc_util.ObjCClass("OMTextEditorView")
          app = objc_util.UIApplication.sharedApplication()
          mainwindow = app.keyWindow()
          mainview = mainwindow.rootViewController().view()
          consoleview = None
          visit = [mainview]
          while visit and consoleview is None:
              v = visit.pop()
              if v.isKindOfClass_(OMTextEditorView):
                  # main editor, skip it
                  continue
              if v.isKindOfClass_(OMTextView):
                  consoleview = v
                  continue
              visit += v.subviews()
          return consoleview
      

      or a similar search, but comparing classname strings:

      def findConsoleView():
          app = objc_util.UIApplication.sharedApplication()
          mainwindow = app.keyWindow()
          mainview = mainwindow.rootViewController().view()
          consoleview = None
          visit = [mainview]
          while visit and consoleview is None:
              v = visit.pop()
              classname = str(v._get_objc_classname())
               if classname == "OMTextEditorView":
                  # main editor, skip
                  continue
              if classname == "OMTextView":
                  consoleview = v
                  continue
              visit += v.subviews()
          return consoleview
      

      now, in Pythonista 3.3, neither of these methods is working...the class-object comparison doesn't match, and it doesn't find a view matching by classname either.

      Anyone know where the console text view has gone?
      I tried looking for all views with "Text" in the classname from the root view, as above, but there's no view that appears to contain the console text.

      cvp mikael 3 Replies Last reply Reply Quote 0
      • cvp
        cvp @shinyformica last edited by

        @shinyformica my script still works

        from objc_util import *
        import ui
        
        @on_main_thread
        def test(sender):
        	import clipboard
        	import console
        	from objc_util import ObjCClass
        	import re
        	import ui
        	txt = str(sender.console.text())
        	if txt[-1] == '\n':
        		txt = txt[:-1]
        	win = ObjCClass('UIApplication').sharedApplication().keyWindow()
        	main_view = win.rootViewController().view() 
        	ret = ''
        	def analyze(v):
        		for tv in v.subviews():
        			if 'OMTextView' in str(tv._get_objc_classname()):
        				su = tv.superview()
        				if 'OMTextEditorView' in str(su._get_objc_classname()):	
        					continue	
        				for sv in tv.subviews():
        					if 'SUIButton_PY3' in str(sv._get_objc_classname()):
        						sv.removeFromSuperview()
        				if txt == '':
        					return
        				t = str(tv.text())
        				#print('search',txt,'in',t)
        				#for m in re.finditer('(?i)'+txt, t):
        				first = True
        				clp = ''
        				for m in re.finditer(txt, t):
        					st,en = m.span()
        					p1 = tv.positionFromPosition_offset_(tv.beginningOfDocument(), st)
        					p2 = tv.positionFromPosition_offset_(tv.beginningOfDocument(), en)
        					rge = tv.textRangeFromPosition_toPosition_(p1,p2)
        					rect = tv.firstRectForRange_(rge)	# CGRect
        					x,y = rect.origin.x,rect.origin.y
        					w,h = rect.size.width,rect.size.height
        					if first:
        						first = False
        						tv.setContentOffset_(CGPoint(0,y))
        					#print(x,y,w,h)
        					l = ui.Button()
        					l.frame = (x,y,w,h)
        					if '|' not in txt:
        						l.background_color = (1,0,0,0.2)
        					else:
        						# search multiple strings
        						wrds = txt.split('|')
        						idx = wrds.index(t[st:en])
        						cols = [(1,0,0,0.2), (0,1,0,0.2), (0,0,1,0.2), (1,1,0,0.2), (1,0,1,0.2), (0,1,1,0.2)]
        						col = cols[idx % len(cols)]
        						l.background_color = col
        					l.corner_radius = 4
        					l.border_width = 1
        					tv.addSubview_(l)
        					clp += t[st:en] + '\n'
        				clipboard.set(clp)
        			ret = analyze(tv)
        			if ret:
        				return ret
        	ret = analyze(main_view)
        
        @on_main_thread
        def FindTextInConsole():
        	global console_tv
        	win = ObjCClass('UIApplication').sharedApplication().keyWindow()
        	main_view = win.rootViewController().view() 
        	ret = '' 
        	next_is_console = False
        	def analyze(v,indent):
        		global next_is_console
        		ret = None
        		for sv in v.subviews():
        			#print(indent,sv._get_objc_classname())		
        			#	try:
        			#		if str(sv.currentTitle()) == 'Clear':
        			#			print(dir(sv))
        			#			# add a target action to clear button?
        			#			my_clear_button = ui.Button()
        			#			my_clear_button.action = test
        			#			sv.addTarget_action_forControlEvents_(my_clear_button, sel('invokeAction:'),8)
        			#	except:
        			#		pass
        			#if 'UIImageView' in str(sv._get_objc_classname()):
        			#	print(sv.image())
        			if 'UILabel' in str(sv._get_objc_classname()):
        				#print(indent,sv.text())
        				if str(sv.text()) == '>':
        					next_is_console = sv
        				else:
        					next_is_console = False
        			elif 'OMTextView' in str(sv._get_objc_classname()):	
        				if next_is_console:
        					su = next_is_console.superview()
        					for ssv in su.subviews():
        						if 'SUIButton_PY3'in str(ssv._get_objc_classname()):
        							# rerun of this script, remove previous button
        							ssv.removeFromSuperview()
        					b = ui.Button(name='clipboard')
        					b.tint_color ='red'
        					b.image = ui.Image.named('iob:ios7_search_32')
        					b.background_color = 'white'
        					h = su.frame().size.height
        					b.frame = (2,2,h-4,h-4)
        					b.action = test
        					b.console = sv
        					retain_global(b)
        					su.addSubview(ObjCInstance(b))
        
        			ret = analyze(sv,indent+'  ')
        			if ret:
        				return ret
        	ret = analyze(main_view,'')
        	return ret
        
        if __name__ == '__main__':	
        	r = FindTextInConsole()
        
        1 Reply Last reply Reply Quote 0
        • cvp
          cvp @shinyformica last edited by

          @shinyformica is that what you want?

          from objc_util import *
          
          def GetConsoleText():
          	print('called')
          	win = ObjCClass('UIApplication').sharedApplication().keyWindow()
          	main_view = win.rootViewController().view()	
          	ret = '' 
          	def analyze(v):
          		ret = None
          		for sv in v.subviews():
          			if 'textview' in str(sv._get_objc_classname()).lower():
          				if 'OMTextEditorView' not in str(sv.superview()._get_objc_classname()):	# not TextView of script
          					return sv.text()
          			ret = analyze(sv)
          			if ret:
          				return ret
          	ret = analyze(main_view)
          	return ret 
          
          1 Reply Last reply Reply Quote 0
          • shinyformica
            shinyformica last edited by

            @cvp so strange...there were definitely some issues in those methods which make it so they honestly shouldn't have been working except by luck. But also, it is definitely now the case that the isKindOfClass_() calls no longer work...which is why these were failing in my tests.

            So this works:

            def findConsoleView(sender):
                app = objc_util.UIApplication.sharedApplication()
                mainwindow = app.keyWindow()
                mainview = mainwindow.rootViewController().view()
                visit = list(mainview.subviews())
                while visit:
                    v = visit.pop()
                    if str(v._get_objc_classname()) == "OMTextView":
                        if v.superview() and not \
                                str(v.superview()._get_objc_classname()) == "OMTextEditorView":
                            return v
                    visit += list(v.subviews())
                return None
            

            but at some point isKindOfClass_() was returning True for me when comparing an actual OMTextView instance against objc_util.ObjCClass("OMTextView").

            So not sure when that stopped working. But this updated version works for my purposes.

            mikael 1 Reply Last reply Reply Quote 0
            • mikael
              mikael @shinyformica last edited by

              @shinyformica, your method returns None for me as well. At the same time, some other methods using isKindOf_ work as they used to (disabling swipe to close and finding the root view).

              1 Reply Last reply Reply Quote 0
              • mikael
                mikael @shinyformica last edited by

                @shinyformica, looking at ”my stuff”, I have always used the .ptr after the ObjC class for them to work. But that does not seem to make a difference for your code. 😕

                1 Reply Last reply Reply Quote 0
                • shinyformica
                  shinyformica last edited by shinyformica

                  That is odd. I haven't tried the isKindOfClass_() call with the .ptr...I don't see it called that way elsewhere in places like objc_util or the Gestures module.

                  What's disturbing is that I'm relying on calling isKindOfClass_() on objc_util.ObjCClass() objects in a lot of places, so now I'm worried it'll fail elsewhere. Those calls usually check against UIView or UIVIewController, so maybe they're safe...and so far I see no issues.

                  This was the only place that was suddenly broken...and given the other issues in the logic, I'm actually pretty confused how my old code was working at all...and yet it was.

                  1 Reply Last reply Reply Quote 0
                  • JonB
                    JonB last edited by

                    Have you swizzled textview to allow custom menu's or anything else?

                    As an aside, I remember something similar when implementing swizzle.
                    To swizzle a class it should be possible to use ObjCClass(classname) to get the class. But for many objects, it doesn't work, and instead I needed to call the runtime getClass method on an instance:

                    cls =ObjCInstance(c.object_getClass(instance.ptr))

                    Although I have no evidence, what I suspect is that objc categories or extensions might be creating new classes.

                    1 Reply Last reply Reply Quote 0
                    • shinyformica
                      shinyformica last edited by

                      @JonB I try very hard not to swizzle anything...it sounds and feels dirty. I did do it once, I admit, in order to make the tableview have the tableView_willDisplayHeaderView_forSection_ method, so I could customize header display.

                      I suspect you are right about why it isn't working. Using the string comparison is working well enough.

                      1 Reply Last reply Reply Quote 0
                      • First post
                        Last post
                      Powered by NodeBB Forums | Contributors