Detect rotation in UIInputViewController subclass. Keyboard extension - ios

I have been developing custom keyboard extension and I need to update few constraints programatically after device rotates. I have been trying to detect user interface rotation in my UIInputViewController subclass but without success. These methods are not called when device rotates:
-(void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>) coordinator {
NSLog(#"Orientation changed");
}
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
NSLog(#"Orientation changed");
}
I've also tried to observe UIDeviceOrientationDidChangeNotification but it doesn't work either.
Does anyone know how to detect rotation in UIInputViewController?

You're correct - those methods are not called in the UIInputViewController subclass for Keyboard Extensions (not sure about other extension types).
You need to override viewDidLayoutSubviews.
For example, if you wanted to update the layout of a UICollectionView subview of your keyboard when the device rotates, you'd do something like this:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self.keyboard.collectionView performBatchUpdates:nil completion:nil];
}
In your case you can check the orientation of the device in viewDidLayoutSubviews, and then apply/modify constraints appropriately.

Swift 4.2
override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
let screen = UIScreen.main.bounds
if screen.width < screen.height {
print("!!! portrait")
} else {
print("!!! landspace")
}
}

It looks like Apple didn't provide this API method to the UIInputViewController.
You can infer the device orientation if the device width is greater than device height in viewWillAppear

Related

traitCollectionDidChange not called when multitasking

I'm working on an adopting multitasking to support split view for app, but I find the traitCollectionDidChange not called when app is on the right.
Does anyone have idea about this?
You may override traitCollectionDidChange in your ViewController.
However.
traitCollectionDidChange called when you change from one split view mode to another. For example from 50/50 view to 33/66. It so NOT called when you enter multitasking mode or exit it.
If you need to handle all events including entering and exiting multitasking mode, use viewWillTransitionToSize:withTransitionCoordinator: instead:
// put this in your ViewController code
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
// TODO: put your code here (runs BEFORE transition complete)
}
If you want your code called AFTER the transition compelete:
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// TODO: put your code here (runs AFTER transition complete)
}];
}
Have you tried the viewWillTransitionToSize method? This is used to notify the container that the size of its view is about to change.
Objective-C
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
Swift
func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animateAlongsideTransition(nil, completion: {
if UIDevice.currentDevice().orientation.isLandscape.boolValue {
print("landscape")
} else {
print("portrait")
}
}
If anyone still doubt that is the point:
// This method called every time user changes separator position or when user rotates device
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
// Always call super in those methods
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
// Before this call your layout is old, status bar orientation will return value before rotation
[coordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context) {
// Code here will be executed during transform. Status bar orientation is new, your view size already changed (in general).
// Setup required animations or custom views transitions
}
completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// Split view transition complete or device rotated
}];
}
Also there is a method traitCollectionDidChange:, but it will be called only when horizontal size class actually changed. For example, if your app presenting from right side in split view mode, traitCollectionDidChange: will not be called when user changed separator position. But if your app presenting from left, it will be called always in portrait mode, and in landscape for transitions (50/50) <-> (66/33)
In view controller, we should call
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
to get notified when the orientation/multi window view changes.
As per documentation of UITraitEnvironment:
The system calls this method when the iOS interface environment changes. Implement this method in view controllers and views, according to your app’s needs, to respond to such changes. For example, you might adjust the layout of the subviews of a view controller when an iPhone is rotated from portrait to landscape orientation. The default implementation of this method is empty.
At the beginning of your implementation, call super to ensure that interface elements higher in the view hierarchy have an opportunity to adjust their layout first.

Preferred way to set frame at run time for different orientation

I have two different UI specs for my iOS app.
I'm facing issues in orientation change(Upsidedown not supporting) for following scenario.FYI :I'm using using self.view.frame.size.width to set width for UI and to identify orientation change i'm using UIDeviceOrientationDidChangeNotification1.Change orientation to landscape
2.change to orientation upsidedown (not settign any frame)
3.change orientation to portrait directly (setting frame for portrait mode)
On following these steps my UI frame is getting misplaced.I'm getting width as 568 for portrait mode.
Please suggest me a better way to fix this problem.
It is not good practice to change the self.view frame when rotating because its bound are handled by the UIViewController rotation logic.
You can use the willRotateToInterfaceOrientation:duration: and didRotateFromInterfaceOrientation: delegate methods for manual rotation.
you are listing to your device orientation changes and you need to allow UI rotation and listen to it with
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
//update your view's subviews
}
note that UIDeviceOrientation is different from UIInterfaceOrientation
to allow interface rotation add this to the view controller and make sure that in the project plist you are supporting the landscape and portrait in the 'Supported interface orientations' key
- (BOOL)shouldAutorotate {
return YES;
}
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskAllButUpsideDown;
}

iOS set screen orientation programmatically, like method - setRequestedOrientation in Android

Background:
My app was configured in portrait view by default, but for one scene the screen should be in landscape view only.
Question:
How to set the orientation manually with codes, in other words, users need not to rotate the device to generate an event to make the screen rotated.
PS:
I need a method like "setRequestedOrientation" in Android or I need to know how to construct a screen rotation event to send to the system.
Have you on the desired view controller tried
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskLandscape;
}
Another thing you can do is to change the orientation on the desired view controller from "Inferred" to "Landscape" in the Attributes inspector in your storyboard.
UIViewController Class Reference
Key:
Use CGAffineTransform CGAffineTransformMakeRotation(CGFloat angle) from CoreGraphics, which could make your view rotated
Sample Codes:
(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.view.transform = CGAffineTransformMakeRotation(M_PI/2);
self.view.frame = CGRectMake(0, 0, 480, 320);
}

UIView rotation text manipulation

I have a view that consists of a bunch of UILabels. As of right now the view is only configured to work when the device's orientation is portrait, however I would like to make it so that when the device is rotated one specific UILabel's location is changed and text size is magnified. When the device's orientation is reverted to portrait, I'd like for it to return to its original state.
How should I go about doing this?
Implement this method in your ViewController and do the changes accordingly.
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {
// do something in landscape orientation
} else {
// do something in portrait orientation
}
}

ScrollView not scrolling when dragging on buttons

I have a scroll view that used to scroll when it didn't have buttons all over it. Now it does, and when dragging the mouse (on simulator) nothing happens (i think because the buttons are being pushed). How can I make this right?
This is happening because UIButton subviews of the UIScrollView (I assume buttons are added as subviews in your case) are tracking the touches and not the scroll view. UIScrollView method touchesShouldCancelInContentView is the key here. According to its description: "The default returned value is YES if view is not a UIControl object; otherwise, it returns NO.", i.e. for UIControl objects (buttons), UIScrollView does not attempt to cancel touches which prevents scrolling.
So, to allow scrolling with buttons:
Make sure UIScrollView property canCancelContentTouches is set to YES.
Subclass UIScrollView and override touchesShouldCancelInContentView to return YES when content view object is a UIButton, like this:
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
{
if ( [view isKindOfClass:[UIButton class]] ) {
return YES;
}
return [super touchesShouldCancelInContentView:view];
}
I founded this question looking for the swift solution for this problem, I "translated" it like this:
Swift 5
class UIButtonScrollView: UIScrollView {
override func touchesShouldCancel(in view: UIView) -> Bool {
if view.isKind(of: UIButton.self) {
return true
}
return super.touchesShouldCancel(in: view)
}
}
hope this could help
Swift 3 Solution
override func touchesShouldCancel(in view: UIView) -> Bool {
if view is UIButton {
return true
}
return super.touchesShouldCancel(in: view)
}
One thing to try if you're seeing this in a simulator is to run on an actual phone. I couldn't scroll in the simulator but no prob on my phone.
In my case, I solved it with this way.
in ViewDidLoad
self.scrollView.panGestureRecognizer.delaysTouchesBegan = self.scrollView.delaysContentTouches;
in .m
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
if ([view isKindOfClass:[UIControl class]]) return YES;
return NO;
}

Resources