dynamic resizing of UICollectionView using intrinsic size within an autolayout - autolayout

I am trying to implement a calendar using UICollectionView which has a similar scrolling behaviour to the built in calendar app when changing months. At the moment the view is flickering as the resize occurs between months.
I am using a UICollectionViewScrollLayout with a UICollectionView with 10000 indexes in 1 section. The cells are filled by calculating the day for an index. The problem I am having is when trying to resize the view to fit the correct number of weeks for the month, the collectionview doesn't appear to draw in the cells quickly enough as the scrolling and view size change happens. Having slowed the animation down, as the view size changes, it appears that cells are being removed too early from the view. This happens both with a reducing and enlarging the view. Ie, as they are about to scroll off the view they are removed before scrolling out of the view.
The layout is all done using autolayout and there is a fixed view above and then a resizable view below. As the size of the collection view changes the view below changes to fill the space. The only way I seem to have managed to achieve this behavior it is by changing the intrinsic size as per the code below. I have tried changing the frame/bounds but it doesn't seem to work with autolayout. Am I going about the resizing of the view in the right way?
Should I be moving the behaviour to the viewcontroller and change the constraints pinning the height instead.
// Scroll the view to the date given.
-(void) moveViewToDate:(NSDate*) date
{
NSIndexPath *indexPath=[self indexPathForFirstDayInMonth:date];
[self scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionTop animated:YES];
[UIView animateWithDuration:0.25f animations:^{
self.isize=[self weeksInViewForDate:date];
[self invalidateIntrinsicContentSize];
[self.superview layoutIfNeeded];
}];
}
-(CGSize)intrinsicContentSize
{
return CGSizeMake(322,self.isize*46);
}

I finally worked out what was going on. Since I was committing an animation which was changing the size of the view and doing a scroll, the two seperate animations where conflicting with each other.
I eventually rewrote the collection using UIScrollView but had the same problem. I got round it by placing the UIScrollView in a UIView. The UIView was resized using a constraint on the height which was animated.

Related

iOS 13 UITableViewCell | Child view gets clipped out

SO I have a UITableView which contains a UITableViewCell and tableView.header is set to a Parallax Header. The UITableViewCell has certain UIViews which are set as negative constraints from Top and are shown perfectly on iOS-12 whereas when observed on iOS-13 the UIViews showing over Parallax Header are clipped. I have tried all possible solutions but unfortunately it still shows the same. My App is opting out of SwiftUI and still running the old code. I have even tried to change the zIndex of the child UIView but still the same. Here are some snapshots and some code:
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.PriceView.layer.zPosition = 1
self.PriceView.bringSubviewToFront(self.contentView)
self.PriceView.setNeedsDisplay()
}
This code is functional for iOS-12 but not iOS-13.
UPDATE:
Apple's changelog/documentation for iOS-13 suggests:
The UITableViewCell class’s contentView property is always laid out
edge-to-edge with adjacent accessories, both on the leading and the
trailing side. This streamlines the layout code so developers who want
the correct default offset no longer have to align their content with
the content view border or the layout margin depending on whether
there is an accessory on the trailing side or not. You should now
always lay out their code on the layout margins of the cell’s content
view to get the default system insets. These insets will be adjusted
automatically based on the accessories visible in the cell to match
the system’s default spacing. (48214114)
This is a bit unclear to me and if someone can help me out here.
Here are the snapshots as to how it is showing in iOS-12 which is required and how it is showing in iOS-13 respectively:
And here observe for iOS-13:
Here is also the Constraint Layout of my UITableViewCell's PriceView:
I need both to be consistent on both OSes. THANK YOU SO MUCH FOR THIS HELP!
Wohoo! I finally figured it out. So before as default it was working on iOS-12 but after iOS-13 and UITableViewCell changes as described in the question, it stopped working. Here is what I did:
Firstly, in the Size Inspector, the ContentView of UITableViewCell was set to Safe Area Layout Guide which I turned off and turned on Safe Area Relative Margins, similarly I did the same for the child, as in the PriceView.
Secondly, in the Attributes Inspector, the ContentView's and UITableViewCell's both had their Autoresize Subviews property turned on, I simply turned it off
And voila! It works. Hope anyone who might have these UITableViewCell issues, please note that now its all relative 😉
Actually, what worked for me was unchecking "clip to bounds" from the attribute inspector for the content view

Xamarin.Forms ScrollView ScrollToAsync behaviour

I have been having issues with using the ScrollToAsync function of a ScrollView when running on Android, not tested on iOS yet.
I'm using Xamarin.Forms version 2.5.1.444934 using .Net Standard
Testing on the Android Emulator (8.0, API26) and a Google Pixel (8.1 API27), both have the same issue.
I use NavigationPage and Navigation.PushAsync to move between pages (therefore have navigation bars on every page).
I want to create an auto populate field control, the user types in the first few letters and I look up the results in a database / static list of string and display the results below for the user to click/tap.
I have this working using a composite control (the contents of the frame marked interesting below).
The problem I have is scrolling the view to show the results.
My page has this layout hierarchy:-
ContentPage
StackLayout
ScrollView
StackLayout
Frame (some label and entry fields inside a stack layout)
Frame (some label and entry fields inside a stack layout)
Frame (some label and entry fields inside a stack layout)
Frame (the interesting one that deals with scrolling)
StackLayout
Heading Label
StackLayout
Label x:Name="ScrollToLabel"
Entry x:Name="ScrollToEntry"
Grid x:Name="ScrollToGrid"
Entry x:Name="Hidden" Text="I should stay visible as you type"
/StackLayout
/StackLayout
/Frame
Frame (some label and entry fields inside a stack layout)
Frame (some label and entry fields inside a stack layout)
/StackLayout
/ScrollView
StackLayout
Button Text = Done
/StackLayout
/StackLayout
/ContentPage
When the user clicks/taps in to ScrollToEntry, the keyboard opens and pans the control to above the keyboard (ok so far).
When the user starts typing in ScrollToEntry, the code populates ScrollToGrid with up to 5 results.
At this point I want to make sure that the grid results are visible for the user to tap on.
I have tried 2 different options, ScrollToAsync(ScrollToLabel, Start) and ScrollToAsync(Hidden, MakeVisible).
Neither work as I would like 100% of the time.
ScrollToAsync(ScrollToLabel, Start)
If the ScrollToEntry is at the bottom of the screen, the navigation bar is hidden and the view pans up too far.
The ScrollToLabel is now off the top of the screen.
When the second letter is clicked/tapped, the Navigation bar is restored and the scroll scrolls correctly, ScrollToLabel at the top of the screen.
If the ScrollToEntry is near the top of the screen, the navigation bar is not hidden and it scrolls correctly.
ScrollToAsync(Hidden, MakeVisible)
This is my preferred option because it would only scroll as far as needed to display all the results (not all the way to the top).
This works even worse than the first one.
In most cases, it does not scroll at all, I assume it thinks it already visible but is actually behind the keyboard?
If it does scroll, the label Hidden is still not displayed (I tested with both a HeightRequest="0" version and a visible version, both the same result).
I tried using the below code to change the adjust to resize.
Application.Current.On<Xamarin.Forms.PlatformConfiguration.Android>().UseWindowSoftInputModeAdjust(WindowSoftInputModeAdjust.Resize);
2 issues,
1. Because it resizes, the Done button now is above the keyboard, taking up space.
2. When clicking the hardware back button (not the done or navigation back button) the previous view now has a white space where the keyboard was.
If using the done or navigation back, the screen initially appears with the white space but then refreshes to full screen.
I have created a sample app that highlights the issues (by default using pan, not resize).
It's the most simple version of the layout I'm using...
Am I doing something wrong or is this just how it works? If its how it works, this must be a bug? If I'm doing it wrong, can someone tell me what or suggest a different way of doing it?

Getting QScrollArea to resize its child widget immediately

I have a QScrollArea (we'll call it myContainer) that contains and scrolls a child view (we'll call it myChildWidget). All works almost fine - when I change the height of myChildWidget dynamically in response to something, if the height exceeds that of myContainer (the QScrollArea), a vertical scroll bar pops into view, effectively narrowing myChildWidget since I've set it to resize its child using:
myContainer->setWidgetResizable(true);
The problem is that myChildWidget is not actually resized until later, rather than right when I set its new height or try resizing myContainer, so I can't do certain things depending on its new width without subclassing and putting in a whole bunch of extra code. Surely there's something I can call to get the QScrollArea to auto-resize its child right away, right? I can use:
QCoreApplication::processEvents();
but the problem with that is that it causes the widgets to flash and redraw when I'm not done setting things up. I've tried update(), updateGeometry(), and adjustSize(), both on the container and its child, and none work. Anything I'm missing? Thanks for any help.

Getting rid of unnecessary scrollbar in QScrollArea

I'm working on a Configuration QDialog. It has several categories (General, Appearance, etc.) that get loaded when the user clicks on them. Each category has its own page. These pages are separate classes in their own right (each has its own ui, cpp, and h). When the Preferences window loads, the pages get instantiated and are loaded into a QStackedWidget. The Stackedwidget is then placed into a QScrollArea so it can scroll if necessary.
However, there is a problem. Ever since I added the QStackedWidget, the QScrollArea always has a vertical scrollbar even when the current page is short enough not to need one: (picture shows the shortest page)
The ScrollArea vertical scroll policy is set to Qt::ScrollBarAsNeeded so logically it should only show a bar if the page is larger than the viewable area.
Here's what I already tried to fix this:
Setting the scroll policy to Qt::ScrollBarAlwaysOff. While this gets rid of the scrollbar, its unacceptable since it doesn't let the user know they need to scroll on long pages.
Setting the Minimum/Maximum heights for the QStackedwidget. This makes the scrollbar go away if I set it for a low enough value, but it is unacceptable since it causes some of the widgets to have a squished appearance.
I know the problem has something to do with the QStackedWidget but since this is the first time I've used QStackedWidget I’m not sure what it is. I've also noticed the scroll is always for the same amount; i.e. the scrollable area is always the same size no matter how large/small the page widget is. For some reason, it's slightly larger than the longest page. At first I thought the vertical spacers I put at at the bottom of each page to tighten up the layout were causing this, but taking them out didn't fix it.
Update: Here's the code that controls the Stackedwidget:
void Newconfig::on_Categories_currentItemChanged(QTreeWidgetItem *current)
{
QModelIndex index=ui->Categories->currentIndex();
int idx=index.row();
QString category=current->text(0);
this->setWindowTitle("Preferences -- " + category);
if (stack->currentWidget() != 0) {
stack->currentWidget()->setSizePolicy(QSizePolicy::Ignored,
QSizePolicy::Ignored);
}
stack->setCurrentIndex(idx);
stack->currentWidget()->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Expanding);
adjustSize();
}
The QStackedWidget takes the size of the largest widget added to it. In your case, the largest page in your preferences dialog is what is influencing the size of the QStackedWidget and is thus forcing the scroll area to show its scroll bar, even when it doesn't appear to be necessary.
To get around this, you can create a slot that is triggered right before you change the current widget in the QStackedWidget that sets the size policy of the page you are leaving to QSizePolicy::Ignored and the size policy of the page you are about to show to whatever you desire for that page -- QSizePolicy::Expanding for instance. An example of this technique is detailed on this page. There's a lot of code in the example, but the important part is here:
void changeCurrent(int idx)
{
if (stackWidget->currentWidget() !=0) {
stackWidget->currentWidget()->setSizePolicy(QSizePolicy::Ignored,
QSizePolicy::Ignored);
}
stackWidget->setCurrentIndex(idx);
stackWidget->currentWidget()->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Expanding);
adjustSize();
}
This function is called to change the current index on the QStackedWidget. It causes the page that was just being viewed to have a size policy that has no influence on the overall size of the QStackedWidget and the page that is about to be viewed to have a size policy of consequence.

Making a Flex DataGrid scroll smoothly

I've noticed that the default behaviour for a DataGrid's vertical scroll bar is to scroll one row at a time. This is all well and good when the rows are all uniform and small (e.g. displaying a single line of text), but gets really ugly as soon as you have rows with variable heights.
I'm curious, is there a way to make DataGrid scrolling "smooth"? For instance, is there a way to have the DataGrid scroll by a set number of pixels, lines of text, etc. rather than scrolling one row at a time?
So far, the only solution I've managed to come up with is to place the DataGrid in a Canvas and have the Canvas do the scrolling instead of the DataGrid. The issue with this approach, though, is that as soon as the Canvas scrolls far enough, the DataGrid headers scroll off-screen. Ideally, I'd like to get the smooth-scrolling nature of the Canvas, but also keep the DataGrid headers visible. Is that possible?
The way that ItemRenderer's work in Flex 3 makes smooth scrolling difficult to achieve. Basically Flex recycles item renderers scrolled off of the top of the list as the display objects used for new data at the bottom of the list. Adobe's implementation of most list components in Flex 3 creates and adds these items as they come on to the screen rather than just off the screen, so they "pop in" and smooth scrolling isn't available. I'm not sure why they couldn't have done it in a similar manner for items +/- one position above or below the current scroll pane, but they didn't, and we're stuck with sticky scrolling by default.
Work-arounds do exist, though the one you've noted (dropping the datagrid into a canvas) negates the display-object saving intention of item renderers and incurs a performance cost. This will be fixed for most list-based Flex components in Flex 4, though it won't be fixed immediately for DataGrid. The DataGrid / AdvancedDataGrid component is maintained by a separate team based in India, last time I heard, and so it tends to be a bit behind the rest of the SDK.
I'd recommend trying something similar to this implementation of a smooth-scrolling list by Alex Harui. I'm not sure exactly how well it'd work for DataGrid or AdvancedDataGrid, but this is the most intuitive technique I can think of for making the list scroll correctly.
Try this... It's still based on Alex's code that was mentioned above. His should still be a great start for removing the snap-to-row behavior. Original source:
http://blogs.adobe.com/aharui/2008/03/smooth_scrolling_list.html
Alex's original some code for smooth vertical scrolling but that was not an issue I had with the DataGrid. It was smooth scrolling horizontally that I needed. I am using the DataGrid in an unorthodox manner for analyzing plain text reports output by our database (great way of providing visual feedback on a document). The code below allows content to go off screen and the user can scroll without that snap-to-column behavior.
You can adapt this to use the same math routines for vertical scrolling and then it will make scrolling possible and ignore the snap to row behavior. In particular switch the usage of the listContent.move method to move the contents vertically and use a inverse of the rounded pixel value you calculate from the vertical scroll bar (as opposed to my using the horizontal).
This method is bit simpler than Alex's method from the link above - a lot less code so try adapting and see how it works.
override protected function scrollHandler(event:Event):void
{
// Override the default scroll behavior to provide smooth horizontal scrolling and not the usual "snap-to-column" behavior
var scrEvt:ScrollEvent = event as ScrollEvent;
if(scrEvt.direction == ScrollEventDirection.HORIZONTAL) {
// Get individual components of a scroll bar for measuring and get a horizontal position to use
var scrDownArrow:DisplayObject = horizontalScrollBar.getChildAt(3);
var sctThumb:DisplayObject = horizontalScrollBar.getChildAt(2);
// I replaced maxHorizontalScrollPosition in Alex's code with "1300" to fix my exact application. In other situations you may finding using some property or different value is more appropriate. Don't rely on my choice.
var hPos:Number = Math.round((sctThumb.y - scrDownArrow.height) / (scrDownArrow.y - sctThumb.height - scrDownArrow.height) * 1300);
// Inverse the position to scroll the content to the left for large reports
listContent.move(hPos * -1, listContent.y);
}
// Go ahead and use the default handler for vertical scrolling
else {
super.scrollHandler(event);
}
}

Resources