iOS 8 Custom Keyboard: issues when changing default keyboard height - ios

Having implemented a similar method described in iOS 8 Custom Keyboard: Changing the Height, I have two main issues that I can't seem to figure out.
1) When a rotation occurs, there is a terribly noticeable jump. The keyboard rotates at its current height, and then only after the rotation has completed does it abruptly jump to the new height.
2) The contents of the keyboard don't seem to be drawn until the rotation is complete. This is causing a whole list of problems when trying to use the various available callbacks. For example, I'm trying to set the contentSize height of my scrollView, which is at the bottom of my keyboard (similar to the old emoji keyboard), to the same height as my scrollView. However,
scrollView.contentSize = scrollView.frame.size.height
does not work because the scrollView (nor its enclosing view - tried that also) hasn't reached its final bounds yet. I've verified this by checking the bounds after the keyboard is completely loaded - only then does the scrollView return the correct value for its height.
The same thing occurs when I try to set the size for the contents of my collectionView (which comprises the main body of the keyboard):
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let heightPortrait: CGFloat = 0.8
let heightLandscape: CGFloat = 0.8
var collectionViewHeight = collectionView.bounds.size.height
var portrait = collectionViewHeight * heightPortrait
var landscape = collectionViewHeight * heightLandscape
var newDimensions: CGFloat
newDimensions = UIScreen.mainScreen().bounds.size.width < UIScreen.mainScreen().bounds.size.height ? portrait : landscape
return CGSize(width: newDimensions, height: newDimensions)
}
I've implemented viewWillTransitionToSize as well so each object will update on rotation, but when it's called, the bounds are once again not yet set:
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
self.setKeyboardHeight() // Change the keyboard height from its default setting.
self.buildScrollView() // Populate the scrollView and set contentSize as well as cell attributes.
println("Before: \(self.view4.frame.size.height)") // Is the same as 'After' (see a few lines below), also swapping out view4 (the scrollView's superview) for scrollView doesn't change anything.
coordinator.animateAlongsideTransition(nil, completion: { context in
self.collectionView?.collectionViewLayout.invalidateLayout()
println("After: \(self.view4.frame.size.height)")
})
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
}
I've been cracking my head over this for way too long, so if anyone can help me figure it out I will be eternally grateful. Thank you so much for your help and any time you may take to answer/work on this!!

Related

Autlolayout not updating frames by the time call to systemLayoutSizeFitting(:withHorizontalFittingPriority:verticalFittingPriority:) is made

I have UITableViewCell subclass. The xib looks like below:
On launching the app I get the cell rendered as below as I change the content view with a custom lock view. The rotation from portrait to landscape is also done:
After rotating from landscape to portrait the layout is weird as below:
In the UITableViewCell subclass I have overridden the method systemLayoutSizeFitting(:withHorizontalFittingPriority:verticalFittingPriority:).
The implementation is as below:
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
print("customContentView.bounds:", customContentView.bounds)
verticalStackView.arrangedSubviews.forEach({
$0.sizeToFit()
$0.layoutIfNeeded()
})
verticalStackView.setNeedsLayout()
verticalStackView.layoutIfNeeded()
let customContentViewHorizontalPadding = customContentViewLeadingConstraint.constant + customContentViewTrailingConstraint.constant
let customContentViewVerticalPadding = customContentViewTopConstraint.constant + customContentViewBottomConstraint.constant
var _adjustedTargetSize = targetSize
_adjustedTargetSize.width -= customContentViewHorizontalPadding
_adjustedTargetSize.height -= customContentViewVerticalPadding
var size = customContentView.systemLayoutSizeFitting(_adjustedTargetSize)
let collectionViewContentSize = collectionView.collectionViewLayout.collectionViewContentSize
size.height += collectionViewContentSize.height
size.height += customContentViewVerticalPadding
size.width += customContentViewHorizontalPadding
return size
}
Upon testing the rotation from landscape to portrait I have found that the target size sent in to the method has a larger width then required for portrait, which results in a smaller height calculation and messing up the interface.
Why is the system passing in a larger width then required in this rotation?. How do I correct this?
Note: If I remove the override of the method the collection view inside the stack view seems to be totally ignored in the calculation as evident with the image below:
systemLayoutSizeFitting is not something you override, it’s something you call. And a stack view is already self sizing from its contents so your code would be pointless in any case. As this is a table cell, just configure constraints and let the runtime size the height.

IOS 11: UICollectionView on iPhone X is sized wrong

I have a collection view that the frame appears too tall on the iPhone X. On every other device, the sizing and scrolling works properly as shown below:
However on iPhone X, it looks like this:
The top row is cut off, and it does not scroll all the way down to the last row. Somehow, the sizing correctly calculates the width but not the height, which is about 70 pixels too tall. (I'm not worried about the top and bottom bars. I'll fix those later)
I'm guessing this has something to do with the inset adjustments for the iPhone X screen, but I can't figure out how to fix it. I've tried this in where I size the collection view:
if #available(iOS 11.0, *) {
collectionView.contentInsetAdjustmentBehavior = .always
}
However, I can't seem to fix it.
Edit:
To clarify, the set up for this menu is as follows, there are actually two collection views onscreen. The first scrolls horizontally with paging enabled so that it locks onto each cell. The other collection views are the cells for the first one, and they scroll vertically. We'll call these subCollectionViews.
The subCollectionViews are receiving a size from the original collection view thats too tall. On the storyboard, the collection view's height is defined with respect to the top bar and the bottom paging bar as flush. In the story board, the height of the collection view is about 70 pixels larger than the calculated height during runtime.
So for the cell's layout guide:
override func awakeFromNib() {
cellImage = UIImageView(frame: contentView.frame)
cellImage?.contentMode = .scaleAspectFill
cellImage?.clipsToBounds = true
contentView.addSubview(cellImage!)
}
and for the collection view's layout:
layout.scrollDirection = .vertical
layout.minimumLineSpacing = 8
layout.minimumInteritemSpacing = 8
for the storyboard presets:
I think this is what y'all asked for. If there's anything you need to see, just ask.
I also facing this issue. Need to called safeAreaLayoutGuide for control the size of item.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if #available(iOS 11.0, *) {
return view.safeAreaLayoutGuide.layoutFrame.size
} else {
// Fallback on earlier versions
return view.frame.size
}
}
Add below lines of code. I think it will solve your problem.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.aCollectionVew.setNeedsLayout()
self.aCollectionVew.layoutIfNeeded()
}
Please take all traits, spacing and contentInsetAdjustmentBehavior.
Happy Coding...:)

Default UICollectionView layout not respecting iPad split screen

My layout when on split screen is not respecting the width of the split screen.
My custom view is respecting it(the black bar at the top) but anything using autolayout is not respecting the width.
I am handling rotation using
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
Is there a helper method for handling split view? Do I handle it in layoutSubview? I would have expected the UICollectionView to handle this for us.
In viewWillTransition I use
guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
return
}
flowLayout.invalidateLayout()
I assume by "default" layout you mean a UICollectionViewFlowLayout. It looks like the itemSize width is bigger then the width of the view. Try adding a check in collectionView:layout:sizeForItemAtIndexPath: to make sure that the width is less than or equal to the collection's width.
Yes. That issue still exist in iOS 13 beta 7.
If you implement multiple-window and split two collection view side by side. The only left window will layout correct (rotate several times could see that).
Add that in view controller can fix it:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
collectionViewLayout.invalidateLayout()
}

Equal height of UICollectionViewCell's and UICollectionView

How am I supposed to set the height of the cells in UICollectionView equal to the height of whatever the collection view is? The code below doesn't work because the collection views height is not known at this point it seems since the auto layout is messing with the properties at this stage. It causes the cells height to be higher than the actual collection view.
I will add 300 in bounty to an answer that solves this!
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSize(width: 100, height: collectionView.frame.size.height)
}
2015-12-16 18:43:53.643 My-app[1055:434762] the behavior of the enter
code hereUICollectionViewFlowLayout is not defined because: 2015-12-16
18:43:53.643 My-app[1055:434762] the item height must be less than the
height of the UICollectionView minus the section insets top and bottom
values, minus the content insets top and bottom values. 2015-12-16
18:43:53.643 My-app[1055:434762] Please check the values return by the
delegate. 2015-12-16 18:43:53.644 My-app[1055:434762] The relevant
UICollectionViewFlowLayout instance is , and it is attached to ; layer = ; contentOffset: {0, 0}; contentSize: {3087, 307}>
collection view layout: .
2015-12-16 18:43:53.644 My-app[1055:434762] Make a symbolic breakpoint
at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the
debugger.
Solution that works based on hannads suggestion. If there are better ways please let me know
Made a property with a property observer.
var myCollectionViewHeight: CGFloat = 0.0 {
didSet {
if myCollectionViewHeight != oldValue {
myCollectionView.collectionViewLayout.invalidateLayout()
myCollectionView.collectionViewLayout.prepareLayout()
}
}
}
Override this method (it is called multiple times)
override func viewDidLayoutSubviews() {
myCollectionViewHeight = myCollectionView.bounds.size.height
}
Then I have this in my delegate:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSize(width: 100, height: myCollectionViewHeight)
}
Here is how I would do it. First add a global variable in the view controller which will be used to store the height of the collection view. In the viewDidLayoutSubviews method, set this variable to the height of the collectionView, and if it changed, call a method to invalidate the UICollectionView layout
collectionView.collectionViewLayout.invalidateLayout()
Which will call the method to set sizes of the collectionView cells, and in that method, set the height of the cell to the global variable holding the height of the collectionView.
Note: currently on my mobile and did not test this code. I might have missed something.
You try set this code to your viewDidLoad:
self.automaticallyAdjustsScrollViewInsets = false
Hope this help!
I had been struggling with this issue for over a week and finally found an answer that worked for my specific case. My issue was a result of 1) loading remote images in the UICollectionViewCell's image, and 2) having an estimated cell size that was larger than what I set manually on the sizeForItemAt of the collection view layout.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.bounds.width, height: collectionView.bounds.height)
}
I was able to solve by reducing the cell size on the storyboard to an amount lower than my cell's size, and I also set "Estimate Size" to "None" (this last part is probably enough)
In my case, I have to set collectionViewLayout.estimatedItemSize to an explicit value (which has its height <= collection view's height) instead of UICollectionViewFlowLayout.automaticSize.
The UICollectionViewFlowLayout.automaticSize results a default 50pt by 50pt cell size, which is higher than my collection view. In this case, UIKit refuses to do any layout work, despite that the actual cell size after self-sizing calculation will be smaller.
// horizontally scrollable collection view
private var viewHeight: CGFloat?
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if viewHeight != view.bounds.height {
viewHeight = view.bounds.height
layout.estimatedItemSize = CGSize(width: 50, height: viewHeight!)
layout.invalidateLayout()
}
}
I had this issue because I had pinned my UICollectionView to the bottom of a UITableViewCell without giving the collectionView a height.
After making a few network requests and then calling [self.tableView reloadData] in the UIViewController, I got the same error message in the log.
I managed to resolve it by breaking the bottom constraint and giving the UICollectionView a fixed height.
I ran into this issue as well and i was able to solve it by subtracting the relevant sectionInsets. Once you subtract the insets that aren't considered in the .size.height property it accepts the height.
Inside sizeForItemAtIndexPath you need to subtract the section inset dimensions...
let sectionInset = self.collectionView?.collectionViewLayout.sectionInset
let heightToSubtract = sectionInset!.top + sectionInset!.bottom
return CGSize(width: 100.0, height: (self.collectionView?.bounds.height)! - heightToSubtract)
NOTE: I am force unwrapping the optionals here but to be safe you might want to check them
In my case , I add collectionview to tableviewcell, and collectionview item height is equal to tableviewcell height. when change tableviewcellheiht, i get this warning.
to solve this , try this code
if (#available(iOS 11.0, *)) {
_collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
}
This problem can be easily solved by setting the collectionViewLayout itemSize property inside your viewdidload:
collectionViewLayout.itemSize = .init(width: 80, height: 80)
I solved this problem with this solution:
Cell size is equal collectionView size
2. Change in storyboard "Inset from:" from Safe Area to Content Inset
Change "Content Insets" to Never

Why does UICollectionView log an error when the cells are fullscreen?

I have a UICollectionViewController using a UICollectionViewFlowLayout where my itemSize is the size of the UICollectionView. Basically, this is a line layout of cells where each cell is fullscreen and scrolls horizontally.
In my UICollectionViewFlowLayout subclass, I have overridden prepareLayout as follows:
- (void)prepareLayout {
self.itemSize = self.collectionView.frame.size;
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.collectionView.pagingEnabled = YES;
self.minimumLineSpacing = 0.0;
self.minimumInteritemSpacing = 0.0;
self.sectionInset = UIEdgeInsetsZero;
self.footerReferenceSize = CGSizeZero;
self.headerReferenceSize = CGSizeZero;
}
The UICollectionViewController is very basic returning 10 items in one section. I've included a sample project on GitHub for more detail.
Everything appears to be set up correctly. It looks right in the simulator and on the device but, when the collection view is displayed, there is an error logged to the console:
the behavior of the UICollectionViewFlowLayout is not defined because:
the item height must be less that the height of the UICollectionView minus the section insets top and bottom values.
Note also that the collection view controller in my example is in a navigation controller and while that doesn't look particularly necessary in the example, in my real-world case I need the collection view in a navigation controller.
There is a property on UIViewController–automaticallyAdjustsScrollViewInsets–that defaults to YES. This means that when a UIViewController has a UIScrollView in its view hierarchy–which is true of a UICollectionViewController–the contentInset property of that scroll view is adjusted automatically to account for screen areas consumed by the status bar, navigation bar, and toolbar or tab bar.
The documentation for that property states:
automaticallyAdjustsScrollViewInsets
Specifies whether or not the view controller should automatically adjust its scroll view insets.
#property(nonatomic, assign) BOOL automaticallyAdjustsScrollViewInsets
Discussion
Default value is YES, which allows the view controller to adjust its scroll view insets in response to the screen areas consumed by the status bar, navigation bar, and toolbar or tab bar. Set to NO if you want to manage scroll view inset adjustments yourself, such as
when there is more than one scroll view in the view hierarchy.
The solution is to set automaticallyAdjustsScrollViewInsets to NO somewhere in your UICollectionViewController subclass, such as in viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
self.automaticallyAdjustsScrollViewInsets = NO;
}
I have put an example project on GitHub that illustrates this problem and solution. There are two branches: with_error and fixed_error. Here is a diff of the change on GitHub.
iOS 11 update: automaticallyAdjustsScrollViewInsets is deprecated in iOS 11.0.
Apple recommends using UIScrollView's contentInsetAdjustmentBehavior method instead. I set this value to .never and the error has gone. You can also set this property in Interface Builder.
collectionView.contentInsetAdjustmentBehavior = .never
This issue just occured to me on 3x screens (namely the iPhone 6 Plus.) As it turned out, the autolayout engine did not really like infinite floating point values (such as .33333333), so my solution was to floor the return height in sizeForItemAt:indexPath:.
return CGSize(width: preferredWidth, height: floor(preferredHeight))
I encountered this problem when rotating the device from portrait to landscape, back to portrait. You want to invalidate the collectionView's layout upon device rotation and before the call to super, like so:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
// Causes collection view cells to be resized upon orientation change.
// Important that this is called *before* call to super in order to prevent error from being logged to console.
[self.collectionView.collectionViewLayout invalidateLayout];
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
//...
}
This way had worked for me perfectly!.
I just subtracted the top and bottom insets from the view's height as said in that error.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width , height: view.frame.height - (view.safeAreaInsets.top + view.safeAreaInsets.bottom))
}
I hope it helps!
If you have collectionView inside scrollView just put .invalidateLayout method inside viewDidLayoutSubviews as shown below:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.collectionViewLayout.invalidateLayout()
}
I found out that .invalidateLayout method inside viewWillTransitionToSize doesn't change collection view bounds on orientation change in some cases.
A fix that worked for me.
collectionViewLayout.estimatedItemSize = .zero
or do it via IB:
Estimated Size: None
If you create the collection view in the IB, the Estimated Size property (estimatedItemSize) is set to Auto. The docs say it's .zero by default but it's not.
Like Stunner, I had the same problem when rotating from landscape (picture using full width) to portrait mode. His suggestion was the only one which really helped.
Attached the code with latest Swift for his ObjC example ... and as a bonus, my code to find the center cell of the collection view. Works quite nice ;-)
/**
-----------------------------------------------------------------------------------------------
viewWillTransition()
-----------------------------------------------------------------------------------------------
*/
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
// find cell in the center of the screen and store it in a global variable
let center = self.view.convert((self.collectionView!.center), to: self.collectionView)
// get the indexPath for the cell in the center of the current screen
if let index = collectionView!.indexPathForItem(at: center) {
// store it in a class property
self.indexPathOfCenterCell = index
}
// force recalculation of margins, borders, cell sizes etc.
self.collectionView?.collectionViewLayout.invalidateLayout()
// inform UIKit about it
super.viewWillTransition(to: size, with: coordinator)
}
ios 10: topmost view was not connected to the view outlet
In my case I had property
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
set in my flow layout. So I had to switch it to some obviously wrong constant (but with some explicit height) to suppress this warning.
UICollectionView is such a thing-in-itself and sometimes absolutely unpredictable
I had similar issue.
After load cell which is full width and some height of screen. on some condition I changed the height of cell then I was getting the same error
to fix this
I used
func updateHeightPerRatio(with image:UIImage) {
let ratio = collectionView.bounds.width / image.size.width
constHeightCollectionView .constant = ceil(image.size.height * ratio)
collectionView.reloadData()
collectionView.performBatchUpdates({
collectionView.layoutIfNeeded()
}) { (completed) in
self.collectionView.reloadData()
self.layoutIfNeeded()
}
}
Solution is reload data then perform batchupdate with that collection view re -calculate the frames . after that reload collectionview again it will apply calculated frames to cell
And now there is no log for issue now.
Hope it is helpful
I was getting the same error when I was trying to embed a UICollectionView in a UITableView. I was generating a new UICollectionView in each UITableView cell, but I did not put any constraints on the height of that UICollectionView. So, when I put a constraint on the height, that error is gone!
In my case I have to reduce bottom inset (from 20 to 0) of cell as I have reduced 20 from height of collectionview
From
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
- return UIEdgeInsets(top: 10, left: 10, bottom: 20, right: 10)
+ return UIEdgeInsets(top: 10, left: 10, bottom: 0, right: 10)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = CGSize(width: 350, height: collectionView.bounds.size.height - 20)
return size
}

Resources