7

I have tried the swift example of JSQMessageViewController inside iOS 11 simulator. Here is the result:screenshot

I have tried using safe area margin and modify the toolbar constraint but there is still no difference. It seems that the toolbar is outside UIWindow (UITextEffectsWindow instead). Is there any solution?

Vishnuvardhan
  • 5,081
  • 1
  • 17
  • 33
Samson Wong
  • 226
  • 2
  • 10
  • Sorry but it's not very clear what you're asking, what are you trying to achieve over all? – Alan Sep 27 '17 at 08:03
  • As you can see in the screenshot, the keyboard is placed at the bottom of the iphone X stimulator instead of following the safe area guide. – Samson Wong Sep 27 '17 at 08:19
  • Likely a bug with JSQ. As you might know, its been deprecated. An effort to replace it is going on over at https://github.com/MessageKit/MessageKit – Oscar Apeland Sep 27 '17 at 09:02
  • Yes I understand this. But currently the MessageKit has not yet been ready for production. So I am interested if there is any developer solved this problem by themselves – Samson Wong Sep 27 '17 at 09:52
  • Anyone has experiences dealing this? – Samson Wong Sep 28 '17 at 17:42
  • A topic about this has been opened here: https://github.com/jessesquires/JSQMessagesViewController/issues/2179 – Tulleb Nov 07 '17 at 17:30

8 Answers8

10

Just add an extension for JSQMessagesInputToolbar

extension JSQMessagesInputToolbar {
    override open func didMoveToWindow() {
        super.didMoveToWindow()
        if #available(iOS 11.0, *), let window = self.window {
            let anchor = window.safeAreaLayoutGuide.bottomAnchor
            bottomAnchor.constraintLessThanOrEqualToSystemSpacingBelow(anchor, multiplier: 1.0).isActive = true
        }
    }
}
Simon Bengtsson
  • 7,573
  • 3
  • 58
  • 87
ERbittuu
  • 968
  • 8
  • 19
5

Guys I have figured it out! Just put the following code in the JSQMessagesInputToolbar.m. It seems that the inputtoolbar is placed in its own window, you need to access its window separately.

-(void) didMoveToWindow{
[super didMoveToWindow];
 if (@available(iOS 11.0, *)) {
     [[self bottomAnchor] constraintLessThanOrEqualToSystemSpacingBelowAnchor:self.window.safeAreaLayoutGuide.bottomAnchor multiplier:1.0].active = YES;
     }
}
Samson Wong
  • 226
  • 2
  • 10
  • 5
    you must be check about window.safeAreaLayoutGuide not nil coz just use above code app crashes while close the screen so add if condition that can check if nil then skip – Nitin Gohel Nov 09 '17 at 09:36
4

I am proposing a fixed fork based on the JSQ latest develop branch commit.

It is using the didMoveToWindow solution. Not ideal but worth to try while waiting for Apple's answer about inputAccessoryView's safe area layout guide attachment, or any other better fix.

You can add this to your Podfile, replacing the previous JSQ line:

pod 'JSQMessagesViewController', :git => 'https://github.com/Tulleb/JSQMessagesViewController.git', :branch => 'develop', :inhibit_warnings => true
Tulleb
  • 8,919
  • 8
  • 27
  • 55
3

This answer is based on JSQMessagesViewController version 7.3.

Note: The code below contains some messy pragma directives to avoid compiler warnings. The code itself is actually quite simple once you see beyond the pragmas.

This seems to solve the problem while still allowing the toolbar to be moved when the software keyboard is presented. I added the following code in my JSQMessagesViewController subclass:

- (void)viewDidLoad {
    [...]

    // To keep the toolbar inside the safe area on iPhone X, we need to install a new constraint that has higher priority than the one
    // JSQMessagesViewController manipulates when adjusting for the keyboard. The `toolbarBottomLayoutGuide` is a private property in our
    // superclass, so it's not straightforward to access it...
    if (@available(iOS 11.0, *)) {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wundeclared-selector"
        NSLayoutConstraint *constraint = [self performSelector:@selector(toolbarBottomLayoutGuide)];
    #pragma clang diagnostic pop
        constraint.priority = 999;
        [self.inputToolbar.bottomAnchor constraintLessThanOrEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor].active = YES;
}

Edit: For Swift users, the following trick should let you call the private objc method:

let constraint = perform(Selector(("toolbarBottomLayoutGuide"))).takeUnretainedValue() as! NSLayoutConstraint
constraint.priority = 999

Edit: The code that adjusts the contentInset of the collectionView is not called after this new constraint is added, so if the chat view contains more messages than fits the screen, the last message bubble is obscured by the input toolbar. I solved this by ensuring the insets are updated by adding the following code in viewDidAppear viewDidLayoutSubviews:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
[self performSelector:@selector(jsq_updateCollectionViewInsets)];
#pragma clang diagnostic pop
Tore Olsen
  • 2,153
  • 2
  • 22
  • 35
  • I don't understand where do you get your `toolbarBottomLayoutGuide` from... – Tulleb Nov 07 '17 at 17:47
  • toolbarBottomLayoutGuide is defined in JSQMessagesViewController, which I'm subclassing. It's defined in the .m file so it's not visible to my implementation, which is why I'm using performSelector and adding the pragma directives. – Tore Olsen Nov 08 '17 at 08:46
  • I guess we're talking of different branches then. I am working on the latest `develop` commit from JSQ. [It looks like it has been removed since.](https://imgur.com/Cj8q6sk) – Tulleb Nov 08 '17 at 09:48
  • 1
    You're right, this solution is based on the 7.3 branch, I forgot about that. Thanks for noticing. – Tore Olsen Nov 08 '17 at 14:10
2

I m having same problem. I m trying to solve it by adding the JSQMessageViewController as a child view on a viewController that has safe area set up.

Being MyJSQMessageViewController a subclass of JSQMessagesViewController:

self.myJSQMessageViewController = [[MyJSQMessageViewController alloc] init];
[self addChildViewController:self.myJSQMessageViewController];
[self.view addSubview:self.myJSQMessageViewController.view];
[self.myJSQMessageViewController didMoveToParentViewController:self];
if (@available(iOS 11.0, *)) {
    self.myJSQMessageViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[ [self.myJSQMessageViewController.view.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor], [self.myJSQMessageViewController.view.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor], [self.myJSQMessageViewController.view.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor], [self.myJSQMessageViewController.view.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor] ]];
}

Not ideal solution but at least you will have input toolbar inside iOS11 safe area... Bad news is that input toolbar wont be displayed on non safe areas so graphically wont be like a default toolbar (see image)

enter image description here

M Penades
  • 1,572
  • 13
  • 24
  • do your collectionView also being scrolled down a little bit when the input textview becomes the first responder? – Samson Wong Oct 03 '17 at 03:41
  • No as far as I can notice. (of course I just tested on emulator) You can see small video on https://imgur.com/PqLx0Mq – M Penades Oct 03 '17 at 15:51
2

Found a solution without empty space below input toolbar on iPhoneX, the idea that not the whole toolbar self should be above the safe area, but only its self.contentView:

-(void) didMoveToWindow{
    [super didMoveToWindow];
    if (@available(iOS 11.0, *)) {
        if (self.window.safeAreaLayoutGuide != nil) {
            [[self.contentView bottomAnchor] constraintLessThanOrEqualToSystemSpacingBelowAnchor:self.window.safeAreaLayoutGuide.bottomAnchor multiplier:1.0].active = YES;
        }
    }
}
Luten
  • 5,420
  • 4
  • 26
  • 24
  • This is a perfect answer.. to check self.window.safeAreaLayoutGuide is nil or not – Hardik Thakkar May 24 '18 at 09:52
  • if (self.window.safeAreaLayoutGuide != nil) { [[self bottomAnchor] constraintLessThanOrEqualToSystemSpacingBelowAnchor:self.window.safeAreaLayoutGuide.bottomAnchor multiplier:1.0].active = YES; } – muditagarwal88 Jun 18 '18 at 15:31
  • @HardikThakkar , another important thing here is setting bottomAnchor on `self.contentView` instead of `self` - toolbar would have extra space when keyboard is hidden, and won't when keyboard is shown – Luten Jun 28 '18 at 10:58
0

Check this response:

Which state:

  • Create an interface of JSQMessagesInputToolbar class, and
  • Override didMoveToWindow method to add this code:

    - (void)didMoveToWindow {
        [super didMoveToWindow];
        NSLog(@"didMoveToWindow");
        if (@available(iOS 11.0, *)) {
    
            UILayoutGuide * _Nonnull safeArea = self.window.safeAreaLayoutGuide;
            if (safeArea) {
                NSLayoutYAxisAnchor * _Nonnull bottomAnchor = safeArea.bottomAnchor;
                [[self bottomAnchor] constraintLessThanOrEqualToSystemSpacingBelowAnchor:bottomAnchor multiplier:1.0].active = YES;
            }
        }
    }
    

Note: Creating interface is preferred over changing the class. Because if you used cocoa pods then each time you updated pods your changes will be overwritten.

I have checked it with iPhone XR (Simulator) and iPhone 7 (Device) it works fine.

rptwsthi
  • 10,094
  • 10
  • 68
  • 109
0

For anyone having issues with the toolbar even after applying the didMoveToWindow fix, it might be due to IQKeyboardManager.

With IQKeyboardManager I've experienced some problems when scrolling down (hiding keyboard) or tapping "intro", so it's better to have it disabled for that specific ViewController where the chat is. In that way, there won't be that undesired margin at the bottom.

Bottom padding on JSQMessagesViewController toolbar

JonyMateos
  • 663
  • 6
  • 10