I'm trying to recreate the built in messaging app's view. I need to add talk bubbles to the bottom as well as prepend them on the top when i click "load previous".
My main issue is that I don't know how to push the rest of the talk bubbles down when i load more to the top. It's been quite a struggle for me.
I'm working in a subclass of UIScrollView and i've added an "innerView" to that.
What i do is add labels(bubbles) to negative values on the top and positive values on the bottom. I store the last positions in "topLabelsPosition" and "bottomLabelsPosition"
Can anyone help with this? Here's my code
CGFloat whereToScroll = 0.0;
CGFloat topOfContent = self.topLabelsPosition.origin.y;
CGFloat bottomOfContent = self.labelsPosition.origin.y;
CGFloat fullHeight = fabs(bottomOfContent)+fabs(topOfContent);
[innerView setFrame:CGRectMake(0,topOfContent,self.frame.size.width, fullHeight)];
if(is_adding_to_top) {
whereToScroll = topOfContent;
} else {
whereToScroll = bottomOfContent;
}
[self setContentSize:contentSize];
CGPoint point = {0, whereToScroll};
[self setContentOffset:point];
My "innerView" does not get bigger on top, but on bottom - i can tell by the background color.
And my scrollview won't scroll to the -300.00 (topOfContent) like i want it to.
I'm open to rewriting whatever and I'm all ears if you'd be so kind as to help.
Thank you so much in advance!
If you look closely at the Messages.app it doesn't scroll when it loads new bubbles at the top. they just blink in. As opposed to new messages appearing in the bottom where it scrolls them in.
I believe they aren't adding them to the top so much as adding them to (0,0) then moving everything already in the scrollView down in the content view. So try not adding to -xxx y but rather 0+ y and adjusting the frames of everything already inside down by the same amount.
Hope this helps.
Related
I have a ScrollView which contains many TableViews aligned horizontally. I would like to be able to swap horizontally to go to the previous/next TableView, however the current TableView always snaps back into the original position. The next/previous TableView is visible mid-swipe.
I've seen other SO questions which suggest checking the contentSize of the ScrollView but I have had no luck with this. Can anyone advise?
Setting scrollView.pagingEnabled = YES or NO did not seem to make any difference either.
- (void)viewDidLayoutSubviews {
self.scrollView.contentSize = CGSizeMake(self.scrollView.bounds.size.width * self.numTableViews, self.scrollView.bounds.size.height);
}
I'm building a view that's very similar to the messages app - I have a subview at the bottom of the page with a UITextView in it and as the user types and reaches the end of the line the text view as well as the view containing it should expand upward.
The way I have it working is that in the textViewDidChange: method I call my layout function, and that does
CGFloat textViewWidth = 200;
CGFloat textViewHeight = [self.textView sizeThatFits:CGSizeMake(textViewWidth, 2000)].height;
[self resizeParentWithTextViewSize:CGSizeMake(textViewWidth, textViewHeight)];
// And then the resize parent method eventually calls
textView.frame = CGRectMake(10, 10, textViewWidth, textViewHeight);
The problem is that when typing at the end of line and the view expands, I end up with an arbitrary contentOffset.y of something like 10.5 on the text view so the text is all shifted up to the top of the view. Weirdly, it's alternating on every other line, so expanding the first time leaves the y content offset shifted up, then at the next line it's close to zero, then back to 10.5 on the next line, etc. (not sure if that's helpful or just a strange artifact of my values). I can set it back to zero afterwards but it looks terrible because there's a brief flash where the text has the offset value and then it gets shifted back to the middle.
I've read that it's usually better to use content insets for scroll views rather than changing the frame, but I don't get how to do that because I do need to change the frame size as well.
How can I resize the UITextView without this happening? I think I can get by with setting the text view not to be scrollable and that fixes the issue, but I'd like to understand what's going on.
The problem is that UITextView's scroll animation and your frame setting action were happened at the same time.
UITextView internally scrolls the texts you currently typing to visible when typed one more character at the end of the line or typed the new line character. But the scroll animation does not need because you are expanding the textview. Unfortunately we can't control textview's internal scroll action so the text scrolls to the top of the expanded textview weirdly. And that weird scroll makes unnecessary bottom padding too.
You can avoid this weird action very simply with overriding UITextView's setContentOffset:animated: like this.
Objective-C
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated {
[super setContentOffset:contentOffset animated:NO];
}
Swift
override func setContentOffset(_ contentOffset: CGPoint, animated: Bool) {
super.setContentOffset(contentOffset, animated: false)
}
This code avoids the auto sizing UITextView's unnecessary scroll animations and you can expand the size of the text view freely.
Setting textView.scrollable = NO lets me resize the text view without any strange offsets, that's the only way I've been able to figure out. And I guess it's not too much of a limitation for common scenarios, if you want the text view to be scrollable you probably don't need to resize it on the fly since the user can scroll around as the content changes.
I confronted the same issue: changing the UITextView's frame to fit its content had a side effect on the scroll position being wrong. The UITextView scrolled even when the contentSize was fitting the bounds.
I ended up with setting scrollEnabled to true and with rolling the content offset back if the UITextView is not actually scrollable
override var contentOffset: CGPoint {
didSet {
if iOS8 {
// here the contentOffset may become non zero whereas it shouldn't be
if !isContentScrollable && contentOffset.y > 0 {
contentOffset.y = 0
}
}
}
}
var isContentScrollable: Bool {
let result = ceil(contentSize.height) > ceil(height)
return result
}
Actually, I faced the same issue and found that actually this happens only when UITextView has any Autolayout constraints. If you will try to use UITextView without applying any Constraint then this will not happen and will work fine. but, as you apply Auto layout constraints it automatically scrolls to bottom. To deal with this I just add method
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
self.textView.contentOffset = CGPointZero;
}
I'm trying to display a custom view like an action sheet in a root view controller within a UINavigationController, but I always end up having the bottom obscured because I don't account for the UINavigationBar + status bar height.
For example the following code will have 64 points worth of bottom part of customView obscured:
CGFloat height = [UIScreen mainScreen].bounds.size.height;
[UIView animateWithDuration:0.5 animations:^{
customView.center = CGPointMake(width / 2.0, height - customViewHeight / 2.0);
}];
I could just use setFrame instead of center but that is irrelevant because it will give me the same results more or less.
I know using constants like -64 pts is not desirable, so is there a away to convert points to the actual screen coordinates? What is the best practice?
Edit (Possibly an additional question)
I'm trying to have a view from outside the bottom of the screen slide in like an UIActionSheet, and every time I try to do this, I always run into the same problem.
I try to set the initial subview frame origin.y to the height of the screen, and have the subview animate to (screenHeight - subviewHeight), which is why I always run into my problem of having the bottom 64 CGPoints obscured.
What's the best way to do this kind of implementation? I've tried searching for sample code, but I've only seen codes using height like CGPoint height = 480.0 - subviewHeight;, which I heard is poor practice.
One way to do this
#implementation UIView (XXScreenConversions)
- (CGPoint)xx_pointFromScreenPoint:(CGPoint)point {
CGPoint inWindow = [self.window convertPoint:point fromWindow:nil];
return [self.window convertPoint:inWindow toView:self];
}
- (CGPoint)xx_screenPointFromPoint:(CGPoint)point {
CGPoint inWindow = [self.window convertPoint:point fromView:self];
return [self.window convertPoint:inWindow toWindow:nil];
}
#end
In addition to Adam's response, I don't think you should rely on the screen height in your calculations here either. Because it's likely the superview you are adding your subview too isn't actually full screen, so it's pushing the bottom off.
If you're slinging rects around, you're better off grabbing the view.bounds of the view you're adding it too and using that for your frame calculations.
If you're using auto layout (if not, learn it cause it's awesome), use the topLayoutGuide.
You are confusing points and pixels. Point is a measure of typography that comes from the days of hot metal typesetting (and it can mean a number of physical measurements). A point in the CGPointMake sense is simply a location on the screen measured in pixels. What you need to be doing is to measure the height value more accurately.
You should have a look at this page in the Apple "iOS 7 UI Transition Guide" to get a handle on how to use helper views like topLayoutGuide in calculating the height of the screen correctly. Very likely you're going to find topLayoutGuide takes care of the 64 pixels perfectly, it always has in my experience.
I have added a UICollectionView which will have 5 cells, scrolling horizontally. I would like the user to be able to scroll between the cells, where each cell will snap to the centre. Here is a my UICollectionFlowLayout code used with the cell sizes etc.
-(UICollectionViewFlowLayout*)collectionLayout{
if (!_collectionLayout) {
_collectionLayout = [[UICollectionViewFlowLayout alloc]init];
_collectionLayout.minimumInteritemSpacing = 0;
_collectionLayout.minimumLineSpacing = 30;
_collectionLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
_collectionLayout.itemSize = CGSizeMake(200, 165);
_collectionLayout.sectionInset = UIEdgeInsetsMake(0, 65, 0, 55);
_collectionLayout.collectionView.pagingEnabled = YES;
}
return _collectionLayout;
}
I have added insets so the first and last cell stops in the middle, though any of the three cells in between don't. Please see attached screen-shots to illustrate -[2 screen shots below]
I can easily achieve centralised paging of the cells if they are the width of the screen or I simply make 5 sections, however if I do this, then the user does not see the other cells left or right, which I need so they know there is more to scroll to, if you know what I mean.
I have read similar answers about disabling paging and using scrollViewWillEndDragging methods, but I could not get these to work either.
If anyone can offer any clear way to do this that would be great,
Thanks in advance
Jim
This likely won't be the solution you're ideally looking for, though it's an option you can use, as I have used for the set up you describe.
Increase your Uicollectionviewcell size to the width of the screen, with your purple square as view centralised within the cell. Remove your section inset code and change line spacing to =0.
At the left and right sides of the cell place arrow images (or as button), or a swipe gesture icon so app user knows there's more. As you've only got 5 cells, have cell at IndexPath.row==0 and IndexPath.row==4 to hide their left and right 'more' arrows respectively.
I'm designing an iPad app that will have a custom grid in it. The grid will display simple geometric shapes, in different colors, that contain a single character. These will update frequently based on user actions.
The grid will be larger than the screen size, in both directions, so will need to scroll/pan.
I also need the top row and first column to be "frozen" - so the top row remains at the top, but the content within it scrolls horizontally with the rest of the grid, and the first column remains at the left, but the content scrolls veritical with the rest of the grid.
The contents of the first column could be wide, so I'll need to allow the user to resize it.
I'm struggling with the best way to design this. I'm thinking that it might be easiest just to have it as a single custom view when I manage all the drawing and interaction manually. But I can't help feeling that I'm missing some easier way of doing it. Maybe there's even a suitable third party component that would be a better starting point.
What would be the best way of designing this component of the app?
I'd have 3 UIScrollViews that you can set the frames for apropriately so that they make the first column and first row the size you wish - the main grid contents can be the bottom right UIScrollView.
The controller class can be the UIScrollViewDelegate for all of the views. Set the contentsize of the 1st column UIScrolView to be the same width as the width of its frame, but height the right size for the content, the 1st row UIScrollView should have the same height as its frame, but the right width for the content, and the bottom left UIScrollView will have it's content size set as the size of teh grid contents.
The - (void) scrollViewDidScroll:(UIScrollView*)scrollView method is called when scrolling any one of them so check which one has scrolled and scroll teh other two as appropriate.
i.e.
//Declared and positioned somewhere earlier, like in the .h
UIScrollView* firstCol;
UIScrollView* firstRow;
UIScrollView* mainGrid;
- (void) scrollViewDidScroll:(UIScrollView*)scrollView {
if (scrollView == firstCol) {
CGPoint offset = mainGrid.contentOffset;
offset.y = firstCol.contentOffset.y
[mainGrid setContentOffset:offset];
}
else if (scrollView == firstRow) {
CGPoint offset = mainGrid.contentOffset;
offset.x = firstRow.contentOffset.x
[mainGrid setContentOffset:offset];
}
else if (scrollView == mainGrid) {
CGPoint offset = firstRow.contentOffset;
offset.x = mainGrid.contentOffset.x;
[firstRow setContentOffset:offset];
offset = firstCol.contentOffset;
offset.y = mainGrid.contentOffset.y;
[firstCol setContentOffset:offset];
}
}
UICollectionView does a pretty good job for it. But if you need a data grid, then look at this one. It is in alfa stage, but it will be quite a powerful in month or two, I would say. Any suggestions are welcome and it's free, of course.