Titanium JIRA Archive
Titanium SDK/CLI (TIMOB)

[TIMOB-26657] iOS: 'masterIsOverlayed' not working when app is in Split View Mode & Landscape

GitHub Issuen/a
TypeBug
PriorityCritical
StatusClosed
ResolutionFixed
Resolution Date2019-01-14T12:39:52.000+0000
Affected Version/sn/a
Fix Version/sRelease 8.0.0
ComponentsiOS
Labelsn/a
ReporterAdam Armstrong
AssigneeVijay Singh
Created2018-12-14T16:28:46.000+0000
Updated2019-04-19T18:38:54.000+0000

Description

See code below. iPad 12.9 inch 2nd generation v12.1 Ti SDK 7.4.1GA *+Scenario A+* Launch App and leave in full screen. - in Landscape - everything is fine. MasterView (on left) and DetailView (on right) are both what you'd expect. - in Portrait - everything is fine. The MasterView (on left) is truly 'overlayed' as defined in my .xml. You can press my Label, 'Toggle Master View', and as expected the MasterView is truly on top and when toggle off you can see the DetailView behind it. *+Scenario B+* Launch App and then go into Split View (by dragging up the dock and opening any other app beside it) and divide the 2 apps 50/50 on the screen. {color:red}- in Landscape - THIS is where the issue exists. When you toggle, you can see the Ti.Gesture 'orientationchange' detects correctly that the view is "Portrait" (due to split screen). This is expected (even though the device is really in landscape - it operates as Portrait as defined by the SplitWindow). HOWEVER, the Master/Detail 'SplitWindow' are not behaving correctly. Try to toggle the MasterView and you'll notice that the DetailView is not behind it. The MasterView is NOT being overlayed as defined.{color} - in Portrait - everything is fine. The MasterView (on left) is truly 'overlayed' as expected and again you can press that toggle label to show the DetailView behind it. XML:
<Alloy>
	<SplitWindow backgroundColor="white" showMasterInPortrait="true" masterIsOverlayed="true" platform="ios" onOpen="onFirstLaunch">
		<!--MASTER View-->
		<NavigationWindow id="masterNavigationWindow" platform="ios">
			<Window title="Menu" barColor="#e4e4e4" backgroundColor="#e4e4e4" tintColor="#0069aa" class="container" layout="vertical">
				<Label text="Label Here" />
			</Window>
		</NavigationWindow>
		
		<!--DETAIL View-->
		<NavigationWindow id="detailNavigationWindow" platform="ios">
			<Window title="SplitView Example" barColor="#ffffff" tintColor="#0069aa" class="container" layout="vertical" backgroundColor="#ff0000" >
				<Label text="Toggle Master View" onClick="toggleMasterView" top="100" />
			</Window>
		</NavigationWindow>
	</SplitWindow>
</Alloy>
JAVASCRIPT
function toggleMasterView(){
	$.masterNavigationWindow.visible = !$.masterNavigationWindow.visible;
}
function onFirstLaunch(){
	setTimeout(function() {
		updateSplitViewUI(Ti.Gesture.portrait, Ti.Gesture.landscape);
	}, 200);
}
/**
 * 
 * @param {Bool} isPortrait
 * @param {Bool} isLandscape
 */
function updateSplitViewUI(isPortrait, isLandscape){		//Ti.API.info("updateSplitView: (isPortrait:" + isPortrait + ",isLandscape:" + isLandscape + ")");
    if (isLandscape) {							//force show the Master Window in Landscape Mode
    		$.masterNavigationWindow.visible = true;
    } 
}
Ti.App.addEventListener('resumed', function(){
	Ti.API.info("App Resumed");
	Ti.Gesture.fireEvent('orientationchange');
});

Ti.App.addEventListener('paused', function(){
	Ti.API.info("App Paused");
});

Ti.Gesture.addEventListener('orientationchange',function(e) {
	updateSplitViewUI(e.source.portrait, e.source.landscape);
});

Attachments

FileDateSize
image.png2018-12-14T18:46:42.000+0000415772
Simulator Screen Shot - iPad Pro (12.9-inch) (2nd generation) - 2018-12-14 at 12.04.02.png2018-12-14T18:19:47.000+00001209031

Comments

  1. Adam Armstrong 2018-12-14

    I was asked to attach a screenshot and/or explain how it impacts or block me. I chose Both. I am looking to extend an existing Titanium iPad app to support SplitView. In the process of planning to do that, and determine the amount of effort, I setup a simple playground app to test how it all works. While doing so, I used a simple inline YouTube Video Player to test the functionality. And in doing so (see attached screenshot), when in landscape orientation and split with another app - you can see that the MasterView takes up a significant portion of the left side of my app. Typically this is where masterIsOverlayed comes in and allows me to show/hide that as needed (just like the Native Apple Mail App) to regain that real estate. For a real-life example - Appcelerator did a write-up a while back on how I was using Titanium to drive our business. In your write-up you can see how much data is on that screen. Now imagine splitting that view in half (for 2 apps side by side) and then imagine losing another 20% of that because masterIsOverlayed isn't working correctly. https://www.appcelerator.com/blog/2015/08/how-amwins-brings-mobile-agility-to-auto-insurance/ Please let me know if you need further information. But at the end of the day - the masterIsOverlayed is not working natively as designed by Apple. (again - see Apple Mail app for an example) Thanks, Adam.
  2. Adam Armstrong 2018-12-14

    Even if you hid that MasterView entirely, or merged the stacked NavigationWindows, either would work better than taking up the space and not allowing the app to use it.
  3. Adam Armstrong 2018-12-14

    *Issue #1 (initial issue listed on this ticket)* * Image titled, "Simulator Screen Shot - iPad Pro (12.9-inch) (2nd generation) - 2018-12-14 at 12.04.02.png" shows how, when in Landscape Mode and using SplitView with another app, the left (MasterView) takes up a significant portion (~20%) of my app and even with masterIsOverlay defined it isn't being overlayed. *Issue #2* * Image titled, "image.png" shows another issue. If you are in Portrait Mode and NOT using SplitView, then when I set the left (MasterView) to visible=false, it DOES hide correctly....but then you can NOT click any of the leftNavButtons on the DetailView. I suspect this is because the left (MasterView) is obstructing the DetailView NavButtons with a hidden Window. We either need a way to click those NavButtons or some other method (than visible=false) to hide the MasterView Overlay.
  4. Adam Armstrong 2018-12-14

    *Issue #3* Look at the doc for SplitWindow - https://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.iOS.SplitWindow In your example there is an eventListener 'visible' that, appears, should be fired when Master/Detail Views show/hide.....but it is not listed in 'Events' I could never get it to fire. Either there is some other defined method to show/hide a view that is not documented or if visible=true/false is the way to show/hide - then that listener is not firing.
  5. Adam Armstrong 2018-12-18

    I have done some more testing. If I go into SplitView with 2 apps side by side and I rotate the device multiple times from Portrait to Landscape and back/forth..... in Landscape (physical) orientation: Titanium reports "Landscape" but that is incorrect. When split with another app, the app should always behave as though it was Portrait, and because Titanium is not reporting that correctly, then in the class TiUIiOSSplitWindow.m - line #122
       if (isPortrait) {
           CGFloat masterWidth = roundf(splitRatioPortrait * refSize.width);
           if (showMasterInPortrait) {
             if (masterIsOverlayed) {
       .....
       
    the top-level else gets executed, instead of the (isPortrait), and inside the else (ie. Landscape) there are no conditions for showMasterInPortrait & masterIsOveralyed. Which is to be expected if truly in Landscape and fullscreen. So, I *think* Issue #1 is due to the incorrect assumption that if it is NOT in Portrait mode (see code/line mentioned above) then it must be Landscape. I think Issue #1 needs an additional condition/check for when it is in Landscape Mode to look for SplitView. My suggestion would be to reverse the logic in lines 122-169 to something like:
        if (isLandscape && !isSplitView) {
             // Side by side. Master+Detail occupy visible area
        } else {
              if (showMasterInPortrait) {
                   if (masterIsOverlayed) {
                         // Detail occupies visible area. Master on top
                   } else {
                        // Side by side. Master+Detail occupy visible area
                   }
              } else {
                   // Side by side. Detail in visible area. Master off screen to left.
              }
        }
       
    I just dont know how enough about Objective-C to know how to determine isSplitView (that I added above)
  6. Adam Armstrong 2018-12-18

    ok. so I took a stab at it and I think I found the solution. In the file: TiUIiOSSplitWindow.m inside: - (void)layoutSubviewsForOrientation:(UIInterfaceOrientation)orientation Add this:
       BOOL isRunningInFullScreen = CGRectEqualToRect([UIApplication sharedApplication].delegate.window.frame, [UIApplication sharedApplication].delegate.window.screen.bounds);
       
    Then flip the logic to this:
       if (!isPortrait && isRunningInFullScreen) {
             /*
              * Side by side. Master+Detail occupy visible area
              */
             CGFloat masterWidth = roundf(splitRatioLandscape * refSize.width);
             detailSize = CGSizeMake(refSize.width - masterWidth, refSize.height);
             masterSize = CGSizeMake(masterWidth, refSize.height);
             masterRect = CGRectMake(0, 0, masterSize.width, masterSize.height);
             masterCenter = CGPointMake(masterSize.width / 2, masterSize.height / 2);
             detailRect = CGRectMake(0, 0, detailSize.width, detailSize.height);
             detailCenter = CGPointMake(masterSize.width + (detailSize.width / 2), detailSize.height / 2);
         }  else  {
           CGFloat masterWidth = roundf(splitRatioPortrait * refSize.width);
           if (showMasterInPortrait) {
             if (masterIsOverlayed) {
               /*
                        * Detail occupies visible area. Master on top.
                        */
               detailSize = CGSizeMake(refSize.width, refSize.height);
               masterSize = CGSizeMake(masterWidth, refSize.height);
               masterRect = CGRectMake(0, 0, masterSize.width, masterSize.height);
               masterCenter = CGPointMake(masterSize.width / 2, masterSize.height / 2);
               detailRect = CGRectMake(0, 0, detailSize.width, detailSize.height);
               detailCenter = CGPointMake(detailSize.width / 2, detailSize.height / 2);
             } else {
               /*
                        * Side by side. Master+Detail occupy visible area
                        */
               masterSize = CGSizeMake(masterWidth, refSize.height);
               masterRect = CGRectMake(0, 0, masterSize.width, masterSize.height);
               masterCenter = CGPointMake(masterSize.width / 2, masterSize.height / 2);
               detailSize = CGSizeMake(refSize.width - masterSize.width, refSize.height);
               detailRect = CGRectMake(0, 0, detailSize.width, detailSize.height);
               detailCenter = CGPointMake(masterSize.width + (detailSize.width / 2), detailSize.height / 2);
             }
       
           } else {
             /*
                    * Side by side. Detail in visible area. Master off screen to left.
                    */
             detailSize = CGSizeMake(refSize.width, refSize.height);
             masterSize = CGSizeMake(masterWidth, refSize.height);
             masterRect = CGRectMake(0, 0, masterSize.width, masterSize.height);
             masterCenter = CGPointMake(-masterSize.width / 2, masterSize.height / 2);
             detailRect = CGRectMake(0, 0, detailSize.width, detailSize.height);
             detailCenter = CGPointMake(detailSize.width / 2, detailSize.height / 2);
           }
         }
       
  7. Joshua Quick 2018-12-18

    Hold on. Ti.Gesture is supposed to provide the device's orientation, not the app's orientation. You already have the ability to detect the app's orientation. You can do so by comparing the window's width/height like the below.
       window.addEventListener("postlayout", function() {
       	var isPortrait = (window.size.height >= window.size.width);
       });
       
    But Ti.Gesture is the only means of determining the device's orientation. This API is important for fixed-orientation apps (such as portrait-only) that want to rotate the UI themselves. This is typically how a camera app works. Perhaps my above comments might be unrelated to your specific issue, but I wanted to make it clear how Ti.Gesture orientation is supposed to work.
  8. Adam Armstrong 2018-12-18

    I am most likely confusing the terminologies. on an iPad, rotate to landscape, and open apple mail app. you see a splitview. now slide up dock and open messages in split view. you'll instantly see the apple mail split view adjust and hide the MasterView - only showing the DetailView. if I use your event listener above - it only hides the MasterView and doesnt give the DetailView the entire width of the app. All I am trying to say is that, when using masteroverlay, then it should truly be overlayed meaning the detailview takes up the entire screen.
  9. Adam Armstrong 2018-12-18

    if you are saying I should use my own logic/eventlistener like you did above, then we need methods to show/hide the masterView and expand/restrict the detailview size accordingly. I dont see those in the docs.
  10. Joshua Quick 2018-12-18

    We may be talking about 2 different things here. Sorry. I agree that the overlay should fill the window. _(I'm not an iOS developer here. I'll wait for someone on the iOS team to comment on this.)_ I just want to make it clear how Ti.Gesture orientation is supposed to work. It's an often misused feature. Let's forget about master overlay for the moment. If you hold the device "landscape" and put it into split-screen mode, Ti.Gesture will still return "landscape" (this is the device orientation) even though the app is displayed "portrait". This would be the correct behavior. On an Android tablet, split-screen mode gets a bit more interesting since you can drag the split-bar, which would resize the 2 apps... and can resize them to such as degree that 1 app would be landscape and the other portrait. My point being that using Ti.Gesture device orientation to layout content isn't the way to go. You have to look at the app window's size instead. (Probably not related to your master overlay issue, but wanted to make my point clear.)
  11. Adam Armstrong 2018-12-18

    I agree Ti.Gesture is correct. I was referring to orientation in the context of the SplitView determination of 'portrait' vs 'landscape' and the fact that if in landscape and split view - then it should *behave* as though portrait (meaning the master view is overalayed. Sorry for the confusion but yes, I agree with you the Ti.Gesture API us devs use appears to be fine. I wasn't sure how to describe the window constraints within splitview, but maybe you explained it best with your event listener example.
  12. Joshua Quick 2018-12-18

    Okay. I get what you're saying. Sorry if I added confusion to this. :)
  13. Adam Armstrong 2018-12-18

    Ha. No apology needed - I mis-used the terms.
  14. Vijay Singh 2018-12-19

    [~amwinsauto] Thanks for raising the issue. Issue 1- In [document](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.iOS.SplitWindow) it is mentioned that- "The property masterIsOverlayed controls how the master is displayed in portrait mode. When masterIsOverlayed is set to true, the detailView occupies the full screen and and masterView is displayed overlayed on top to the left of the screen. When masterIsOverlayed is set to false, the available screen width is split between the masterView and detailView". Which means that masterIsOverlayed property is supported for portrait mode only. But I agree with you that masterIsOverlayed should be supported in landscape mode while using SplitView with another app. Issue 2- This is a bug. Need to look in this. Issue 3- 'visible' event is not supported in SDK. I am not sure why it is in document. Need to update document. Do you have any suggestion?
  15. Vijay Singh 2018-12-19

    Classic app test case -
        
        var button = Ti.UI.createButton({ title: 'Click' });
        
        var detail = Ti.UI.createWindow({ 
            backgroundColor: 'white',
            leftNavButton: button
        });
        var label1 = Ti.UI.createLabel({ text: 'Toggle Master View' });
        detail.add(label1);
        var detailNav = Ti.UI.createNavigationWindow({ window: detail });
        
        
        var master = Ti.UI.createWindow({ backgroundColor: 'gray' });
        var label2 = Ti.UI.createLabel({ text: 'Master View' });
        master.add(label2);
        var masterNav = Ti.UI.createNavigationWindow({ window: master });
        
        var splitWin = Ti.UI.iOS.createSplitWindow({
            detailView: detailNav,
            masterView: masterNav,
            showMasterInPortrait: true,
            masterIsOverlayed: true
        });
        splitWin.open();
        
        button.addEventListener('click', function(e){
            Ti.API.info("Left button clicked");
        });
        
        label1.addEventListener('click', function(e){
            masterNav.visible = !masterNav.visible;
        });
        
        splitWin.addEventListener('open', function(e){
            onFirstLaunch();
        });
        
        function onFirstLaunch(){
            setTimeout(function() {
                updateSplitViewUI(Ti.Gesture.portrait, Ti.Gesture.landscape);
            }, 200);
        }
        /**
         * 
         * @param {Bool} isPortrait
         * @param {Bool} isLandscape
         */
        function updateSplitViewUI(isPortrait, isLandscape){        //Ti.API.info("updateSplitView: (isPortrait:" + isPortrait + ",isLandscape:" + isLandscape + ")");
            if (isLandscape) {                          //force show the Master Window in Landscape Mode
                    masterNav.visible = true;
            } 
        }
        Ti.App.addEventListener('resumed', function(){
            Ti.API.info("App Resumed");
            Ti.Gesture.fireEvent('orientationchange');
        });
         
        Ti.App.addEventListener('paused', function(){
            Ti.API.info("App Paused");
        });
         
        Ti.Gesture.addEventListener('orientationchange',function(e) {
            updateSplitViewUI(e.source.portrait, e.source.landscape);
        });
        
  16. Adam Armstrong 2018-12-19

    Issue #1 - I submitted a PR (attached to this ticket) that, from my testing, appears to provide masterIsOverlayed when in Landscape AND using SplitView with another App. Issue #2 - After more testing, it is not only the NavButtons that are not-clickable. When you hide the MasterView, anything on that "section" of the DetailView (that was initially covered by the MasterView when Overlayed) is completely unclickable.
  17. Vijay Singh 2018-12-20

    Issue #2 is due to [masterViewWrapper](https://github.com/appcelerator/titanium_mobile/blob/3c757bf8b913c1957f164b47dbccfa5ab8b3f39b/iphone/Classes/TiUIiOSSplitWindow.m#L58). When we hide navigation window, masterViewWrapper is still there. Thats why that area is not clickable. In my view we should introduce a new property "masterViewVisible" in TiUIiOSSplitWindow, which will ensure show and hide of masterView. [~amwinsauto] [~jquick] any thought? Thanks!
  18. Adam Armstrong 2018-12-20

    Sounds like that should work.
  19. Vijay Singh 2018-12-24

    PR (master) - https://github.com/appcelerator/titanium_mobile/pull/10559 PR (8_0_X) - https://github.com/appcelerator/titanium_mobile/pull/10595
  20. Keerthi Mahalingam 2019-01-15

    PR merged
  21. Keerthi Mahalingam 2019-01-15

    Verified the fix in SDK 8.0.0.v20190114160512 and 8.1.0.v20190115063333.'masterIsOverlayed' works as expected.
        Test Environment:
        
Operating System
Name = Mac OS X

        Version = 10.13.6

        Architecture = 64bit

        Node.js
        
Node.js 
        Version = 8.12.0
        
npm Version = 6.4.1

        Titanium CLI
        
CLI Version = 5.1.1
        
STUDIO =5.1.2.201812191857
        Device =iPad Pro 12.9 inch version 12
        

JSON Source