Forum Archive

iPad Orientation Control help

stephen

Any chance anyone knowsvhow i can do one of the following?

  • access to rotation lock setting
  • force rotation programmatically

i use iPad Air 2 (4,5) on iPadOS 13.4.1 And some basic knowledge of objc_util. I did see the comment by @omz

"Important: The orientations parameter has no effect on iPadOS starting with iOS 10 because it is technically not possible to lock the orientation in an app that supports split-screen multitasking."

But there must be a way to control orientation.. Thank you in advance!

mcriley821
from objc_util import *

@on_main_thread
def rotate(to_orientation):
    orientations={
        'Portrait' : 1,
        'PortraitUpsideDown' : 2,
        'LandscapeLeft' : 3,
        'LandscapeRight' : 4
    }
    device.setOrientation_(orientations[to_orientation])
    return

#def toggle_orientation_lock():
    #can’t figure out how to implement this
    #need to find a way to remove supported orientations 
    #that aren’t the current orientation

device=ObjCClass('UIDevice').currentDevice()

This is all I could get to so far. As you can see in my comments, I haven’t been able to find a way to remove/change the supported orientations in a UIViewController object.

stephen

@mcriley821

i found an answernat Stackoverflow but im sure this wont fix it.

found alot of people working around hisbin XCode but thats not an option for me..

But i think This might be where we start to find the answer..

JonB

Are you trying to prevent rotation?

Or force rotation when the device rotates, even when the rotation lock is set?

Or detect changes to rotation or split layout, to force a re-layout of your view?

stephen

@JonB said:

Are you trying to prevent rotation?

Or force rotation when the device rotates, even when the rotation lock is set?

Or detect changes to rotation or split layout, to force a re-layout of your view?

i would prefer to prevent any rotation andkeep landscape.. but i, for the most part, understand why i cannot just set orientations in present so im assuming the best i can do without XCode isto detect a rotation starting and intercepy a force rotation to landscape..?

stephen

@JonB

i found this but what prints out from inspect.getmembers is not the same as on the developers website..

Edit:

now that i look it over again it makes since y it didnt match lol.. but i should still be able to use traitcollectiondidchange:?

or am i over thinking this and just use ui.View.layout() or scene.Scene.did_change_size()

JonB

I think what you want is this one

https://developer.apple.com/documentation/uikit/uiviewcontroller/1621438-preferredinterfaceorientationfor?language=objc

You would need to make a custom view controller class that implements this. Then present that view controller full screen and add your view as a subview... I think @cvp might have examples of how to present custom view controllers?

cvp

@JonB said:

I think @cvp might have examples of how to present custom view controllers?

Sure? I don't remember but I agree that my memory is older than I am 🤯 😅

cvp

@JonB Seriously, the doc says it is an instance property but also says "The system calls this method when presenting the view controller full screen".
Could it be possible to swizzle it?

cvp

Tried with this NY monkeypatching instead of swizzle because it is an instance method, not a class method. The def is called but no orientation change, and even no full screen.
Of course, it is not the main view controller. Sure, help will be needed.

from objc_util import *
import ui

def preferredInterfaceOrientationForPresentation(_self):#, _sel):
    self = ObjCInstance(_self) # UIViewController
    print('preferredInterfaceOrientationForPresentation:',self)
    return 3    # UIInterfaceOrientationLandscapeLeft

@on_main_thread
def main():

    UIViewController = ObjCClass('UIViewController')

    vc = UIViewController.alloc().init()
    # monkeypatch of UIViewController class instance method
    UIViewController.preferredInterfaceOrientationForPresentation = preferredInterfaceOrientationForPresentation(vc)

    vc.setModalPresentationStyle_(2)    # full screen

    l = ui.Label()  
    l.frame = (10,10,100,32)
    l.text = 'label'
    vc.view().addSubview_(ObjCInstance(l))

    rootvc = UIApplication.sharedApplication().keyWindow().rootViewController()
    rootvc.presentViewController_animated_completion_(vc, True, None)

if __name__ == '__main__':
    main()
mcriley821

@cvp Got this to work by create_objc_class on the view controller to override the method, and by letting the system choose the modal presentation style (setting to 0)

changed your method to:

def preferredInterfaceOrientationForPresentation(_self,_cmd):
    return 3

And changed creation of vc and set modal to:

vc=create_objc_class('vc',UIViewController,methods=[preferredInterfaceOrientationForPresentation]).alloc().init()
vc.setModalPresentationStyle_(0)

edited

cvp

@mcriley821 said:

create_obj_class

--> create_objc_class

JonB

I think he meant to have an alloc().init() in there somewhere. You need an instance

mcriley821

Yea add an .alloc().init(). The anti-spam bot won’t let me add it on there. Sorry for the confusion

cvp

@mcriley821 yes, I already did it

cvp

@mcriley821 I tried this script. I'll let @stephen go on 😇
Thanks for your help. I'm happy we have now a new ObjectiveC guru because I only use it with a lot of tries.
Put in gist because refused as spam???

gist

cvp

@jonb @mcriley821 I don't understand why my monkey patching was not good because the method was also called, proved by the print.

mcriley821

@cvp I’m no guru by any means lmao. It takes many crashes for me before I get something working. I’m steadily learning though.

My only guess on why monkey-patching didn’t work is because we already alloced and inited vc as a regular UIViewController and didn’t create another instance after overwriting the class method. But honestly I’ve never done monkey-patching so I’m not positive what I’m talking about

cvp

@mcriley821 But I was overriding an instance method, not a class method. And it worked because the def was called.

cvp

@mcriley821 I just tested with my old code and modal style 0, and it works in the same way as your code with the new objc class. The only difference is that the _cmd 2nd parameter is not accepted.

mcriley821

@cvp if you do

print(vc.preferredInterfaceOrientationForPresentation())

you still get 1, but if you

print(UIViewController.preferredInterfaceOrientationForPresentation())

You get 3.
Your monkey-patch overwrites the UIViewController object. But if you do

vc.preferredInterfaceOrientationForPresentation=preferredInterfaceOrientationForPresentation(vc)

The print has an error 'int object is not callable'.
Maybe it thinks the method is an attribute, and instead of overwriting the function it just makes an attribute

cvp

@mcriley821 you're right. I 🤐 and 😰

stephen

@cvp @JonB @mcriley821

Wow.. ok let me go over this data and ill see what i come up with. im still learning ObjC/Swift. Thank ya'll so much for your input and time im off to crash Pythonista a few times lol i hope we can get this because it would be nice for everyone to have this ability without XCode..

stephen

@cvp @JonB @mcriley821

Apple said:

objc @property(nonatomic, readonly) UIInterfaceOrientation preferredInterfaceOrientationForPresentation;

Documentation

Just to make sure im understanding properly lol..

we use preferredInterfaceOrientationForPresentation with 2 or more UIInterfaceOrientation set on our UIViewController to limit rotation but allowing rotation to differ from UIStatusBar. and if we dont set this property it will keep the UIViewController set to the current UIInterfaceOrientation of UIStatusBar UIInterfaceOrientation..

Instead of trying to make the View's rotation differ from the status bar. why dont we just control the rotation of UIStatusBar itself? or is that where iPadOS has the issue of ontrolling orientation of apps that supports split-screen multitasking.

JonB

@cvp you cannot monkey patch an objc object, without using the objc runtime (swizzleing). Monkey patching just affects calls in python -- when the system calls a selector it doesn't know anything about the python attribute.

There is a way to swizzle in objc, though as I recall, swizzling a single instance is tricky.

cvp

@stephen wow 🤔🤯🤕🤒

cvp

@JonB Thanks for your explanation.

stephen

@cvp said:

@stephen wow 🤔🤯🤕🤒

lol i just read what i put lol it sure is a bit of a mouth full 😂🤣

stephen

@JonB would you happen to know of any good tutorials/walkthroughs/guides i can checkout on objc_util..? im kinda grasping it but there just somthing missing for me to fully take her home...

stephen

😂 just found this while looking through my list of view controller members lol

attentionClassDumpUser_yesItsUsAgain_althoughSwizzlingAndOverridingPrivateMethodsIsFun_itWasntMuchFunWhenYourAppStoppedWorking_pleaseRefrainFromDoingSoInTheFutureOkayThanksBye_

about died

stephen

😓😴 im at a loss.. i tried:
- setInterfaceOrientation
- shouldAutorotateToInterfaceOrientation
- preferredInterfaceOrientationForPresentation
- viewControllerForRotation().setInterfaceOrientation_
- setModalPresentationStyle
- window_willRotateToInterfaceOrientation

the list goes on for a bit lol

ill get no errors but also nothing changes

JonB

Did you implement @mcriley821 's code, along with the alloc().init()? I haven't but mcriley says it works...

cvp

The gist in one of my posts used @mcriley821 's code.

The method works but not what @stephen wants.
Perhaps, the used ViewController is not the good one.

stephen

@JonB @cvp ya iv been mesing with it for just a min and i cant seem to get it to lock, force rotate or anything lol

cvp

@stephen you're right. I don't know how to do and that's the reason why I saidthat I let YOU go on 😅

stephen

@cvp said:

@stephen you're right. I don't know how to do and that's the reason why I saidthat I let YOU go on 😅

all good i even tried the one JonB mentioned and nothin

JonB

In that case the best option may be to implement a callback on the layout method to re-rotate the view. This could maybe be implemented with a simple Transform.rotate.

stephen

@JonB said:

In that case the best option may be to implement a callback on the layout method to re-rotate the view. This could maybe be implemented with a simple Transform.rotate.

When I detect a rotation from ui.View. layout(self) from the resizing i can just rotate and resize my View object? only negative part is the status bar being in the wrong place? if so id say thats a good compromise lol

JonB

Im thinking:

Root view - uses flex, to detect layout.
-> rotated_view. Fixed size. Uses a Transform.rotation to back out the orientation.
-> everything else is a subview of rotated_view

You can get orientation with a bit of objc, or by using a 1 pixel webview and some JavaScript.

stephen

@JonB

to get orientation if always did:

def Orientation():
    return PORTRAIT if get_screen_size()[0] < get_screen_size()[1] else LANDSCAPE

JonB

If you want to be able to detect upside down landscape etc, versus just screen size, you'd have to use objc, or a webview and some JavaScript to get the viewport rotation.

In objc,, you can get the view controller, then use
[UIViewController interfaceOrientation]

Or,

[[UIDevice currentDevice] orientation]

Or, I believe there is something that grabs the status bar orientation.

stephen

@JonB

awesome thank you! this objc_util stuff is interesting.. lol im starting to get the hang of it. i do want to askmone thing though..

ive been using inspect.getmembers() to list fields and methods.. is this good option or is there a better way?

JonB

https://github.com/jsbain/objc_hacks/blob/master/objc_browser.py

This is a good way to see a complete list of available classes (that is, from loaded frameworks) and their methods. Tapping a method name installs a swizzle to log all calls to that method -- useful for figuring out how to call an omz custom method for instance. Don't tap a method name unless you want said logging.

For viewing instances, your method works. Also, You can use

https://github.com/jsbain/viewbrowser/blob/master/getobjcprops.py

To get all properties/values of an objc instance. There is a table view delegate to show as a table. I probably need to create a workspace browser that ties together the viewbrowser objc oject browsing functionality...

stephen

@JonB

Awesome! That would be a wonderful asset 😀