I create a NSWindowController and NSViewController using Interface Builder, And then, i removed the NSWindow's titlebar so that I can custom Window. i create a class subclass NSWindow And do following things in class.
override var canBecomeKey: Bool {
return true
}
override var canBecomeMain: Bool {
return true
}
i also set these in NSWindowController:
{
self.window?.becomeKey()
self.window?.isMovableByWindowBackground = true
self.window?.isMovable = true;
self.window?.acceptsMouseMovedEvents = true
}
from here, the custom Window is can be dragged,
But when i make the NSViewController as the NSWindowController's ContentViewController, i can not drag the customWindow.
What could be happening here?
I had this problem before, here was my solution that works (it's in objective-C though, so just throwing it out there in case it helps):
#property BOOL mouseDownInTitle;
- (void)mouseDown:(NSEvent *)theEvent
{
self.initialLocation = [theEvent locationInWindow];
NSRect windowFrame = [self frame];
NSRect titleFrame = NSMakeRect(0, windowFrame.size.height-40, windowFrame.size.width, 40);
NSPoint currentLocation = [theEvent locationInWindow];
if(NSPointInRect(currentLocation, titleFrame))
{
self.mouseDownInTitle = YES;
}
}
- (void)mouseDragged:(NSEvent *)theEvent
{
if( self.mouseDownInTitle )
{
NSRect screenVisibleFrame = [[NSScreen mainScreen] visibleFrame];
NSRect windowFrame = [self frame];
NSPoint newOrigin = windowFrame.origin;
NSPoint currentLocation = [theEvent locationInWindow];
// Update the origin with the difference between the new mouse location and the old mouse location.
newOrigin.x += (currentLocation.x - self.initialLocation.x);
newOrigin.y += (currentLocation.y - self.initialLocation.y);
// Don't let window get dragged up under the menu bar
if ((newOrigin.y + windowFrame.size.height) > (screenVisibleFrame.origin.y + screenVisibleFrame.size.height)) {
newOrigin.y = screenVisibleFrame.origin.y + (screenVisibleFrame.size.height - windowFrame.size.height);
}
[self setFrameOrigin:newOrigin];
}
}
The controlling factor here is the view's mouseDownCanMoveWindow property. By default that depends, in turn, on its isOpaque property. If you or one of your superclasses override isOpaque to return true, then the default implementation of mouseDownCanMoveWindow will return false. If you want it to behave differently, then you have to override that, too.
Related
There's a behavior in the Line messenger app (the de facto messenger app in Japan) that I'm trying to emulate.
Basically, they have a modal view controller with a scroll view inside. When the scroll action reaches the top of its content, the view controller seamlessly switches to an interactive dismissal animation. Also, when the gesture returns the view to the top of the screen, control is returned to the scroll view.
Here's a gif of how it looks.
For the life of me, I can't figure out how they did it. I've tried a few different methods, but they've all failed, and I'm out of ideas. Can anyone point me in the right direction?
EDIT2
To clarify, the behavior that I want to emulate isn't just simply dragging the window down. I can do that, no problem.
I want to know how the same scroll gesture (without lifting the finger) triggers the dismissal transition and then transfers control back to the scroll view after the view has been dragged back to the original position.
This is the part that I can't figure out.
End EDIT2
EDIT1
Here's what I have so far. I was able to use the scroll view delegate methods to add a target-selector that handles the regular dismissal animation, but it still doesn't work as expected.
I create a UIViewController with a UIWebView as a property. Then I put it in a UINavigationController, which is presented modally.
The navigation controller uses animation/transition controllers for the regular interactive dismissal (which can be done by gesturing over the navigation bar).
From here, everything works fine, but the dismissal can't be triggered from the scroll view.
NavigationController.h
#interface NavigationController : UINavigationController <UIViewControllerTransitioningDelegate>
#property (nonatomic, strong) UIPanGestureRecognizer *gestureRecog;
- (void)handleGesture:(UIPanGestureRecognizer*)gestureRecognizer;
#end
NavigationController.m
#import "NavigationController.h"
#import "AnimationController.h"
#import "TransitionController.h"
#implementation NavigationController {
AnimationController *_animator;
TransitionController *_interactor;
}
- (instancetype)init {
self = [super init];
self.transitioningDelegate = self;
_animator = [[AnimationController alloc] init];
_interactor = [[TransitionController alloc] init];
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Set the gesture recognizer
self.gestureRecog = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
[self.view addGestureRecognizer:_gestureRecog];
}
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator {
if (animator == _animator && _interactor.hasStarted) {
return _interactor;
}
return nil;
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
if (dismissed == self || [self.viewControllers indexOfObject:dismissed] != NSNotFound) {
return _animator;
}
return nil;
}
- (void)handleGesture:(UIPanGestureRecognizer *)gestureRecog {
CGFloat threshold = 0.3f;
CGPoint translation = [gestureRecog translationInView:self.view];
CGFloat verticalMovement = translation.y / self.view.bounds.size.height;
CGFloat downwardMovement = fmaxf(verticalMovement, 0.0f);
CGFloat downwardMovementPercent = fminf(downwardMovement, 1.0f);
switch (gestureRecog.state) {
case UIGestureRecognizerStateBegan: {
_interactor.hasStarted = YES;
[self dismissViewControllerAnimated:YES completion:nil];
break;
}
case UIGestureRecognizerStateChanged: {
if (!_interactor.hasStarted) {
_interactor.hasStarted = YES;
[self dismissViewControllerAnimated:YES completion:nil];
}
_interactor.shouldFinish = downwardMovementPercent > threshold;
[_interactor updateInteractiveTransition:downwardMovementPercent];
break;
}
case UIGestureRecognizerStateCancelled: {
_interactor.hasStarted = NO;
[_interactor cancelInteractiveTransition];
break;
}
case UIGestureRecognizerStateEnded: {
_interactor.hasStarted = NO;
if (_interactor.shouldFinish) {
[_interactor finishInteractiveTransition];
} else {
[_interactor cancelInteractiveTransition];
}
break;
}
default: {
break;
}
}
}
#end
Now, I have to get that gesture handling to trigger when the scroll view has reached the top. So, here's what I did in the view controller.
WebViewController.m
#import "WebViewController.h"
#import "NavigationController.h"
#interface WebViewController ()
#property (weak, nonatomic) IBOutlet UIWebView *webView;
#end
#implementation WebViewController {
BOOL _isHandlingPan;
CGPoint _topContentOffset;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.webView.scrollView setDelegate:self];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if ((scrollView.panGestureRecognizer.state == UIGestureRecognizerStateBegan ||
scrollView.panGestureRecognizer.state == UIGestureRecognizerStateChanged) &&
! _isHandlingPan &&
scrollView.contentOffset.y < self.navigationController.navigationBar.translucent ? -64.0f : 0) {
NSLog(#"Adding scroll target");
_topContentOffset = CGPointMake(scrollView.contentOffset.x, self.navigationController.navigationBar.translucent ? -64.0f : 0);
_isHandlingPan = YES;
[scrollView.panGestureRecognizer addTarget:self action:#selector(handleGesture:)];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
NSLog(#"Did End Dragging");
if (_isHandlingPan) {
NSLog(#"Removing action");
_isHandlingPan = NO;
[scrollView.panGestureRecognizer removeTarget:self action:#selector(handleGesture:)];
}
}
- (void)handleGesture:(UIPanGestureRecognizer*)gestureRecognizer {
[(NavigationController*)self.navigationController handleGesture:gestureRecognizer];
}
This still doesn't work quite right. Even during the dismissal animation, the scroll view is still scrolling with the gesture.
End EDIT1
That is a custom interactive transition.
First, you need set transitioningDelegate of UIViewController
id<UIViewControllerTransitioningDelegate> transitioningDelegate;
Then implment these two method to
//Asks your delegate for the transition animator object to use when dismissing a view controller.
- animationControllerForDismissedController:
//Asks your delegate for the interactive animator object to use when dismissing a view controller.
- interactionControllerForDismissal:
When drag to top, you start the transition, you may use UIPercentDrivenInteractiveTransition to control the progress during scrolling.
You can also refer to the source code of ZFDragableModalTransition
Image of ZFDragableModalTransition
As explained here the solution is quite complex. The person who answered, #trungduc, programmed a little demo published on github doing the sought behaviour. You can find it here.
The easiest way of making this work is to copy the 4 files found in /TestPanel/Presentation/ in the attached github repository, to your project. Then add the PanelAnimationControllerDelegate to your View Controller containing the scroll view (i.e. using the protocol).
Add the following to your View Controller, to satisfy the protocol:
func shouldHandlePanelInteractionGesture() -> Bool {
return (scrollView.contentOffset.y == 0);
}
Add this to deactivate the bouncing effect at the top:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollView.bounces = (scrollView.contentOffset.y > 10);
}
Set scrollView.delegate = self
Before presenting your View Controller containing the scroll view set the following propreties to your View Controller:
ScrollViewController.transitioningDelegate = self.panelTransitioningDelegate
ScrollViewController.modalPresentationStyle = .custom
If you want to change the size of your ScrollViewController, you will need to comment out the override of the frameOfPresentedViewInContainerView in the PanelPresentationController file (one of the 4). Then in the presentationTransitionWillBegin method, you will need to set let frameOfPresentedViewInContainerView = self.frameOfPresentedViewInContainerView.insetBy(dx: 0, dy: 20) with the wanted inset of dx and dy.
Thank you to trungduc for this amazing solution!!
im working with AMSlideMenu, and i did in the MainVC
// Enabling Deepnes on left menu
- (BOOL)deepnessForLeftMenu
{
return YES;
}
// Enabling Deepnes on right menu
- (BOOL)deepnessForRightMenu
{
return YES;
}
Normally a deepness effect will be in the left and right menu, but nothing happen .
Did anyone tried AMSlideMenu before and had same pb ?
Yes it is working. You just need to observe it. If -deepnessForLeftMenu is enabled you can see that menu will be transform and it shows animation and scaling of left menu.
Put a debugger check in AMSlideMenuMainViewController.m class for below method and you will see the differences.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Put a break point here.
if (self.leftMenu && [self deepnessForLeftMenu])
{
self.leftMenu.view.layer.transform = kMenuTransformScale;
self.leftMenu.view.layer.opacity = kMenuLayerInitialOpacity;
}
if (self.rightMenu && [self deepnessForRightMenu])
{
self.rightMenu.view.layer.transform = kMenuTransformScale;
self.rightMenu.view.layer.opacity = kMenuLayerInitialOpacity;
}
}
Also the deepnessForLeftMenu bool variable used in other two methods for the same class
- (void)configure3DTransformForMenu:(AMSlideMenu)menu panningView:(UIView *)panningView
{
float cx = 0;
float cy = 0;
float cz = 0;
float opacity = 0;
// Put a break point here.
/********************************************* DEEPNESS EFFECT *******************************************************/
if (menu == AMSlideMenuLeft && panningView.frame.origin.x != 0 && [self deepnessForLeftMenu])
{
// Codes for deepness effect
}
}
If you set NO for deepnessForLeftMenu variable. You will able to see that menu will show flat animation.
I have to remove this bar as here link but for iOS 7 this code does not work.
We remove this bar with some Objective C runtime trickery.
We have a class which has one method:
#interface _SwizzleHelper : NSObject #end
#implementation _SwizzleHelper
-(id)inputAccessoryView
{
return nil;
}
#end
Once we have a web view which we want to remove the bar from, we iterate its scroll view's subviews and take the UIWebDocumentView class. We then dynamically make the superclass of the class we created above to be the subview's class (UIWebDocumentView - but we cannot say that upfront because this is private API), and replace the subview's class to our class.
#import "objc/runtime.h"
-(void)__removeInputAccessoryView
{
UIView* subview;
for (UIView* view in self.scrollView.subviews) {
if([[view.class description] hasPrefix:#"UIWeb"])
subview = view;
}
if(subview == nil) return;
NSString* name = [NSString stringWithFormat:#"%#_SwizzleHelper", subview.class.superclass];
Class newClass = NSClassFromString(name);
if(newClass == nil)
{
newClass = objc_allocateClassPair(subview.class, [name cStringUsingEncoding:NSASCIIStringEncoding], 0);
if(!newClass) return;
Method method = class_getInstanceMethod([_SwizzleHelper class], #selector(inputAccessoryView));
class_addMethod(newClass, #selector(inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method));
objc_registerClassPair(newClass);
}
object_setClass(subview, newClass);
}
The equivalent of the above in Swift 3.0:
import UIKit
import ObjectiveC
var swizzledClassMapping = [AnyClass]()
extension UIWebView {
func noInputAccessoryView() -> UIView? {
return nil
}
public func removeInputAccessoryView() {
var subview: AnyObject?
for (_, view) in scrollView.subviews.enumerated() {
if NSStringFromClass(type(of: view)).hasPrefix("UIWeb") {
subview = view
}
}
guard subview != nil else {
return
}
//Guard in case this method is called twice on the same webview.
guard !(swizzledClassMapping as NSArray).contains(type(of: subview!)) else {
return;
}
let className = "\type(of: subview!)_SwizzleHelper"
var newClass : AnyClass? = NSClassFromString(className)
if newClass == nil {
newClass = objc_allocateClassPair(type(of: subview!), className, 0)
guard newClass != nil else {
return;
}
let method = class_getInstanceMethod(type(of: self), #selector(UIWebView.noInputAccessoryView))
class_addMethod(newClass!, #selector(getter: UIResponder.inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method))
objc_registerClassPair(newClass!)
swizzledClassMapping += [newClass!]
}
object_setClass(subview!, newClass!)
}
}
I've made a cocoapod based on this blog post from #bjhomer.
You can replace the inputaccessoryview and not just hide it. I hope this will help people with the same issue.
https://github.com/lauracpierre/FA_InputAccessoryViewWebView
You can find the cocoapod page right here.
I've came across this awesome solution, but I needed to get the inputAccessoryView back as well. I added this method:
- (void)__addInputAccessoryView {
UIView* subview;
for (UIView* view in self.scrollView.subviews) {
if([[view.class description] hasSuffix:#"SwizzleHelper"])
subview = view;
}
if(subview == nil) return;
Class newClass = subview.superclass;
object_setClass(subview, newClass);
}
It does seem to work as intended with no side effects, but I can't get rid of the feeling that my pants are on fire.
If you want Leo Natan's solution to work with WKWebView instead of UIWebView just change prefix from "UIWeb" to "WKContent".
I created a gist to accomplish this:
https://gist.github.com/kgaidis/5f9a8c7063b687cc3946fad6379c1a66
It's a UIWebView category where all you do is change the customInputAccessoryView property:
#interface UIWebView (CustomInputAccessoryView)
#property (strong, nonatomic) UIView *customInputAccessoryView;
#end
You can either set it to nil to remove it or you can set a new view on it to change it.
Keep in mind, this also uses private API's, so use at your own risk, but it seems like a lot of apps do similar things nonetheless.
I'm using a UIPageViewController with Navigation set to Horizontal, Transition Style set to Scroll (in InterfaceBuilder), and no spine. Which gives me a lovely UIPageControl integrated. Now I want to be able to toggle whether it's displaying (because there's artwork underneath it).
I've tried setting presentationCountForPageViewController and presentationIndexForPageViewController to return 0 when the UIPageControl is supposed to be hidden, but those methods aren't being called when I want.
Pausing for stacktrace, I see them being called by [UIPageViewController _updatePageControlViaDataSourceIfNecessary]...I assume my app would be rejected if I tried to use that method.
Should I hunt through subviews for it, or roll my own so I have control over it, or is there some better way to toggle its visibility?
Thanks!
I would say, hunt through the subviews. This code successfully finds the UIPageControl in the subviews hierarchy:
NSArray *subviews = pageController.view.subviews;
UIPageControl *thisControl = nil;
for (int i=0; i<[subviews count]; i++) {
if ([[subviews objectAtIndex:i] isKindOfClass:[UIPageControl class]]) {
thisControl = (UIPageControl *)[subviews objectAtIndex:i];
}
}
I'm using this to customize the color of the dots, I imagine you could do the same with the alpha value or send it to the back or something.
Apple provides no direct interface to the UIPageControl through the UIPageViewController class, but there are no illegal method calls required in order to get to it... I don't see why this would result in an app rejection.
You can access this for all PageControl objects by using appearance (see the UIAppearance protocol), but to get a specific instance you'd have to use recursion. Swift code:
let pageControl = UIPageControl.appearance()
Swift 3 Extension:
extension UIPageViewController {
var pageControl: UIPageControl? {
for view in view.subviews {
if view is UIPageControl {
return view as? UIPageControl
}
}
return nil
}
}
In Swift:
let subviews: Array = self.pageViewController.view.subviews
var pageControl: UIPageControl! = nil
for (var i = 0; i < subviews.count; i++) {
if (subviews[i] is UIPageControl) {
pageControl = subviews[i] as! UIPageControl
break
}
}
I implemented a category to handle this for me which gets the mess out of my code and allows me to access the pageControl via "pageController.pageControl"
Objective-C
// Header
#interface UIPageViewController (PageControl)
#property (nonatomic, readonly) UIPageControl *pageControl;
#end
I also used recursion (handled by blocks) in case Apple decides to change the implementation causing the UIPageControl to not be in the first layer of subviews.
// Implementation
#import "UIPageViewController+PageControl.h"
#implementation UIPageViewController (PageControl)
- (UIPageControl *)pageControl
{
__block UIPageControl *pageControl = nil;
void (^pageControlAssignBlock)(UIPageControl *) = ^void(UIPageControl *blockPageControl) {
pageControl = blockPageControl;
};
[self recurseForPageControlFromSubViews:self.view.subviews withAssignBlock:pageControlAssignBlock];
return pageControl;
}
- (void)recurseForPageControlFromSubViews:(NSArray *)subViews withAssignBlock:(void (^)(UIPageControl *))assignBlock
{
for (UIView *subView in subViews) {
if ([subView isKindOfClass:[UIPageControl class]]) {
assignBlock((UIPageControl *)subView);
break;
} else {
[self recurseForPageControlFromSubViews:subView.subviews withAssignBlock:assignBlock];
}
}
}
#end
This may be overkill for your needs but it worked well for mine
How about a nice, up to date Swift 1-liner?
let pageControl = view.subviews.first { $0 is UIPageControl } as? UIPageControl
Or if you like an extension:
extension UIPageViewController {
var pageControl: UIPageControl? {
return view.subviews.first { $0 is UIPageControl } as? UIPageControl
}
}
For Swift
To get the dots in the page control we can use
//dots will be an array of the dots views
let dots = pageControl.subviews
To get the current dot view
let currentDot = dots[pageControl.currentPage]
To get the other dots views
for i in 0..<dots.count {
let dot = dots[i]
if i == pageControl.currentPage {
//dot => current dot
} else {
//dot => other dot
}
}
After we get the dot view we can change whatever we want like
dot.layer.borderColor = .green
dot.layer.borderWidth = 1
C# extension:
public static class PageViewControllerExtension{
public static UIPageControl GetPageControl(this UIPageViewController pageViewController){
foreach (var view in pageViewController.View.Subviews){
var subView = view as UIPageControl;
if (subView != null){
return subView;
}
}
return null;
}
}
The iPad programming guide says that the splitView's left pane is fixed to 320 points. But 320 pixels for my master view controller is too much. I would like to reduce it and give more space to detail view controller. Is it possible by anyway?
Link to the document which speaks about fixed width.
If you subclass UISplitViewController, you can implement -viewDidLayoutSubviews and adjust the width there. This is clean, no hacks or private APIs, and works even with rotation.
- (void)viewDidLayoutSubviews
{
const CGFloat kMasterViewWidth = 240.0;
UIViewController *masterViewController = [self.viewControllers objectAtIndex:0];
UIViewController *detailViewController = [self.viewControllers objectAtIndex:1];
if (detailViewController.view.frame.origin.x > 0.0) {
// Adjust the width of the master view
CGRect masterViewFrame = masterViewController.view.frame;
CGFloat deltaX = masterViewFrame.size.width - kMasterViewWidth;
masterViewFrame.size.width -= deltaX;
masterViewController.view.frame = masterViewFrame;
// Adjust the width of the detail view
CGRect detailViewFrame = detailViewController.view.frame;
detailViewFrame.origin.x -= deltaX;
detailViewFrame.size.width += deltaX;
detailViewController.view.frame = detailViewFrame;
[masterViewController.view setNeedsLayout];
[detailViewController.view setNeedsLayout];
}
}
In IOS 8.0 you can easily do this by doing the following:
1. In your MasterSplitViewController.h add
#property(nonatomic, assign) CGFloat maximumPrimaryColumnWidth NS_AVAILABLE_IOS(8_0);
2. In your MasterSplitViewController.m viewDidLoad method add
self.maximumPrimaryColumnWidth = 100;
self.splitViewController.maximumPrimaryColumnWidth = self.maximumPrimaryColumnWidth;
This is a really good, simple and easy feature of IOS 8.
this code is work for me
[splitViewController setValue:[NSNumber numberWithFloat:200.0] forKey:#"_masterColumnWidth"];
No.
There are two private properties
#property(access,nonatomic) CGFloat masterColumnWidth;
#property(access,nonatomic) CGFloat leftColumnWidth; // both are the same!
but being private mean they can't be used for AppStore apps.
iOS 8 introduced a new property:
// An animatable property that can be used to adjust the maximum absolute width of the primary view controller in the split view controller.
#property(nonatomic, assign) CGFloat maximumPrimaryColumnWidth NS_AVAILABLE_IOS(8_0); // default: UISplitViewControllerAutomaticDimension
Use this property to adjust your master viewcontroller to your desired width.
Here is how I did this in iOS8 with Swift.
class MainSplitViewController: UISplitViewController {
override func viewDidLoad() {
self.preferredDisplayMode = UISplitViewControllerDisplayMode.AllVisible
self.maximumPrimaryColumnWidth = 100 // specify your width here
}
}
If you need to change the width dynamically from within your master/detail view in the split view, then do something like this:
var splitViewController = self.splitViewController as MainSplitViewController
splitViewController.maximumPrimaryColumnWidth = 400
The storyboard way would be this one, mentioned by #Tim:
Furthermore, if you want the Master view to always take up a certain percentage of the screen then you can use the Key Path = "preferredPrimaryColumnWidthFraction" instead and set the value to 0.2 (for 20% screen size).
Please note that the "maximumPrimaryColumnWidth" is set to 320, so if you try the screen percent value of 0.5 (50%) it won't go above 320. You can add a key path for maximumPrimaryColumnWidth if you need to override this.
None of the answers worked for me on iOS7, so I did some of my own research and created a working solution. This will involve subclassing UISplitViewController for the full functionality.
I will present the answer as if we just created a new project for iPad with all device orientations and have set the custom UISplitViewController as the main view controller.
Create your custom UISplitViewController. In this example mine is called MySplitViewController. All code will be based in MySplitViewController.m.
We're going to need to access a method from the UISplitViewControllerDelegate so add that and set the delegate. We'll also setup a delegate forwarder incase you need to call the delegate methods from another class.
#interface MySplitViewController () <UISplitViewControllerDelegate>
#property (nonatomic, weak) id<UISplitViewControllerDelegate> realDelegate;
#end
#implementation MySplitViewController
- (instancetype)init {
self = [super init];
if (self) {
self.delegate = self;
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
self.delegate = self;
}
return self;
}
- (void)setDelegate:(id<UISplitViewControllerDelegate>)delegate {
[super setDelegate:nil];
self.realDelegate = (delegate != self) ? delegate : nil;
[super setDelegate:delegate ? self : nil];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
id delegate = self.realDelegate;
return [super respondsToSelector:aSelector] || [delegate respondsToSelector:aSelector];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
id delegate = self.realDelegate;
return [delegate respondsToSelector:aSelector] ? delegate : [super forwardingTargetForSelector:aSelector];
}
Setup the master and detail view controllers.
- (void)viewDidLoad {
[super viewDidLoad];
UIViewController* masterViewController = [[UIViewController alloc] init];
masterViewController.view.backgroundColor = [UIColor yellowColor];
UIViewController* detailViewController = [[UIViewController alloc] init];
detailViewController.view.backgroundColor = [UIColor cyanColor];
self.viewControllers = #[masterViewController, detailViewController];
}
Lets add our desired width to a method for easy reference.
- (CGFloat)desiredWidth {
return 200.0f;
}
We'll manipulate the master view controller before presenting it.
- (void)splitViewController:(UISplitViewController *)svc popoverController:(UIPopoverController *)pc willPresentViewController:(UIViewController *)aViewController {
id realDelegate = self.realDelegate;
if ([realDelegate respondsToSelector:#selector(splitViewController:popoverController:willPresentViewController:)]) {
[realDelegate splitViewController:svc popoverController:pc willPresentViewController:aViewController];
}
CGRect rect = aViewController.view.frame;
rect.size.width = [self desiredWidth];
aViewController.view.frame = rect;
aViewController.view.superview.clipsToBounds = NO;
}
However, now we're left with a display like this.
So were going to override a private method. Yes a private method, it will still be acceptable in the App Store since its not an underscore private method.
- (CGFloat)leftColumnWidth {
return [self desiredWidth];
}
This deals with portrait mode. So a similar thing for -splitViewController:willShowViewController:invalidatingBarButtonItem: and you should be set for landscape.
However none of this will be needed in iOS8. You'll be able to simply call a min and max width property!
use the following code before assigning to the rootviewcontroller. It works for me with ios7
[self.splitViewController setValue:[NSNumber numberWithFloat:256.0] forKey:#"_masterColumnWidth"];
self.window.rootViewController = self.splitViewController;
Since no one mentioned that this can be done from IB, I want to add this answer. Apparently, you can set "User Defined Runtime Attributes" for the UISplitViewContorller with following details:
Key Path:masterColumnWidth
Type: Number
Value: 250
In my case, I had to set both maximum and minimum to make this work
mySplitViewController.preferredDisplayMode = .allVisible;
mySplitViewController.maximumPrimaryColumnWidth = UIScreen.main.bounds.width/2;
mySplitViewController.minimumPrimaryColumnWidth = UIScreen.main.bounds.width/2;
You can use GSSplitViewController. This one will work on iOS 7 and 8
splitView = [[GSSplitViewController alloc] init];
splitView.masterPaneWidth = 180;
You can also include it by adding pod 'GSSplitViewController' to your Podfile.
ViewController.h
#property(nonatomic, assign) CGFloat maximumPrimaryColumnWidth NS_AVAILABLE_IOS(8_0);
ViewController.m
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
if (SYSTEM_VERSION_LESS_THAN(#"10.0")) {
[self setValue:[NSNumber numberWithFloat:200.0]forKey:#"_masterColumnWidth"];
}else{
self.maximumPrimaryColumnWidth = 200;
self.splitViewController.maximumPrimaryColumnWidth = self.maximumPrimaryColumnWidth;
}
Swift 3.0 you use like
let widthfraction = 2.0 //Your desired value for me 2.0
splitViewController?.preferredPrimaryColumnWidthFraction = 0.40
let minimumWidth = min((splitViewController?.view.bounds.size.width)!,(splitViewController?.view.bounds.height)!)
splitViewController?.minimumPrimaryColumnWidth = minimumWidth / widthFraction
splitViewController?.maximumPrimaryColumnWidth = minimumWidth / widthFraction
let leftNavController = splitViewController?.viewControllers.first as! UINavigationController
leftNavController.view.frame = CGRect(x: leftNavController.view.frame.origin.x, y: leftNavController.view.frame.origin.y, width: (minimumWidth / widthFraction), height: leftNavController.view.frame.height)
// in UISplitViewController subclass
// let more space for detail in portrait mode
- (void)viewWillLayoutSubviews
{
CGFloat width;
if (UIInterfaceOrientationIsPortrait(UIApplication.sharedApplication.statusBarOrientation)){
width = CGRectGetWidth(self.view.bounds) * 0.25f;
}
else {
width = CGRectGetWidth(self.view.bounds) * 0.33f;
}
width = (NSInteger)fminf(260, fmaxf(120, width));
self.minimumPrimaryColumnWidth = width;
self.maximumPrimaryColumnWidth = width;
[super viewWillLayoutSubviews];
}
This code work for me:)
#interface UISplitViewController(myExt)
- (void)setNewMasterSize:(float)size;
#end
#implementation UISplitViewController(myExt)
- (void)setNewMasterSize:(float)size
{
_masterColumnWidth = size;
}
#end
and use it on each operation with view (like rotation)