Set label text in navigation title when scrolling - ios

In the storyboard, I have a label and many other objects in a UIScrollView. (The UIScrollView is in the view).
I want to set the navigation bar's title to the title of the label when the label scrolls past it.
I've tried this code but it doesn't work:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if(self.lbl.frame.origin.x==60) {
self.navigationController!.navigationBar.topItem!.title = lbl.text
}
}
What should i do? I am new to Swift.

First off, I suspect you're actually trying to find the label's y value (i.e. vertical offset) as opposed to its x value, i.e. self.lbl.frame.origin.x==60.
Secondly you don't want the label's frame since that CGRect represents the label's position within the scrollview (which won't change); instead, you want the label's position within the superview.
That said, try this:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if(scrollView.convert(lbl.frame.origin, to: self.view).y <= 60) {
navigationController!.navigationBar.topItem!.title = lbl.text
}
}

There are several problems with your code:
self.lbl.frame.origin.x==60 What are the chances that this will ever be true? That's like flipping a coin and hoping it lands on its edge. The top of the label will never be exactly at a certain point.
The label's origin will never change; scrolling a scroll view doesn't change the frame of its subviews. You need to look at the scroll view's content offset (i.e. how far is it scrolled).
You need to complete your tests. What should happen when the user scrolls back the other way? Also, do you want to change the navigation item title repeatedly, every instant the user is scrolling, or just once?

Related

UIScrollView, detect scrollable area height without scrolling all the way down

I am trying to log some events when a user scrolls the 50% of the scrollView contents.
I can get the scroll amount with scrollView.contentOffset.y as following:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
NSLog(#"%f",scrollView.contentOffset.y);
}
Swift:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset.y)
}
As I scroll down to the bottom of the scrollView I can find out about the height of contentArea once I hit the bottom, lets say that value is 2000 and I can hardcode the value of 1000 since I am interested only when user exceeds the 50% of the whole contentSize.
But I want it to be dynamic, since dataSource of tableView might change in the future, which would result in the bottom of scrollView's offset to be greater or less than 2000.
I would like to know if it is possible to detect the maximum contentOffset value for a tableView even before scrolling to the bottom?
Thanks in advance.
There is a property named "contentSize" of the UIScrollView.
https://developer.apple.com/documentation/uikit/uiscrollview/1619399-contentsize
You may use this after you populate your table view to get the total height your table view can take.
NOTE: This is valid only if your rows have constant height rather than a variable height

How to change the height of the collection view in swift

I have a collection view and a text field on a ui view.
Initially, I have a text field at the top of the ui view and then I set the collection view top Anchor constraint to the bottom of the text field.
I want to implement a behavior where if I scroll down the collection view, the text field should disappear and the collection view's top should be at the top of the same ui view (this would hide the text field). When I scroll up, I want the constraints to be like the initial ones (container view's top Anchor should be set to the bottom of the text field). Any hint at how I might implement this behavior? I was hoping to implement this by updating the constraints when scrolling up and scrolling down happens. How can I implement this?
UICollectionView subclasses UIScrollView, so you can implement a UIScrollViewDelegate to get the scroll events.
Here is some code I use in one of my apps
The bottom of the text field is constrained to the top of the collection view
The top of the text field is constrained to the view's top layout guide - This is the constraint that is modified by the scroll view delegate code
As the collection view scrolls up the text field will move up and fade out
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let textHeight = self.textField.frame.size.height + 2
let offset = min(scrollView.contentOffset.y, textHeight)
if offset >= 0 || self.textFieldConstraint.constant != 0 {
self.textFieldConstraint.constant = -offset
let percent = max(1 - offset/textHeight,0)
self.textField.alpha = percent
}
}
}
Essentially you "push" the text field off the top of the screen, no more than its height+2
I think this solution can work full for you try this, just add a UIScrollView view as parent and put your text field and collection view in scroll view, as set the constraint as you set.

UIScrollView weird behavior with UITextField/UIPickerView

So I'm pretty puzzled right now because my UIScrollView is acting pretty weirdly.
Basically, I have a UIScrollView that is about twice the height of an iPhone 6 that I am using to display some graphs using iOS Charts and so I've set it to be able to scroll and bounce vertically, but neither scroll nor bounce horizontally. All of the graphs, and some additional UITextFields and UILabels are embedded on a separate "body view" that is aligned with the frame of the UIScrollView as seems to be common practice. Thus, the hierarchy of my views looks like this:
This worked well until I noticed today that when I press a specific UITextField on this UIScrollView, which triggers a UIPickerView, all of the sudden my scroll view starts to allow horizontal bouncing. This behavior does not occur for the two other UITextField's on the body view when they are tapped.
I've checked all of the code that is being triggered by tapping on the affected text field, and nothing is directly editing the frames or bounds of any UI objects. In fact, the only function called when the text field is tapped on is the textFieldDidBeginEditing. I've attached the code for this function below, but I am fairly certain it is not the problem.
My next suspicion was that the UIPickerView popping up has been messing with the dimensions of my scroll view and/or it's embedded view. I'm not quite sure if this is possible/probable, but this whole thing has left me pretty stumped.
Here's my code:
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == overallTimeframeTextField {
...
// Not the problematic text field
} else if textField == subjectTimeframeTextField {
...
// Also not the problematic text field
} else { // Affected text field
// Set the text of the text field
if textField.text == "" {
// This is executed in this scenario
textField.text = subjectPickerData[0]
} else {
...
}
}
}
Here is a short GIF outlining my issue. You can see me scrolling down the page, where I am not able to bounce horizontally, and then once I tag on the text field, all of the sudden the scroll view allows the bounces.
GIF
I'm pretty lost with this issue, so any help would be appreciated.
Edit
To clarify, I explicitly declare my scrollView's content size to be equal the desired height and the width of the screen that the user is on. I then set the bodyView's width to equal the same value. This is done in viewDidAppear with the following code:
// Fit the content to the screen
scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: 1200)
bodyView.frame.size.width = UIScreen.main.bounds.width
I also have constraints which force the scrollview and body view to both have the same width as the UIViewController's default child view (the parent of the scroll view in my hierarchy).
One interesting thing that I've noticed is that when I print the width of my scroll view and my body view when the views load, I receive the following output for iPhone 6:
385.0
385.0
This is correct as that is the width of an iPhone 6. However, when I tap on the text field, and then print the same values, I get this output:
385.0
384.0
So for some reason, my body view is one point smaller than my scroll view. I've tried setting the body view's width to be equal to the scroll view when I tap on the text field, like I do in the viewDidAppear function, but this had no effect.
In terms of the UIPickerView, I initialize a pickerview with my class instance variables like so:
var subjectPickerView = UIPickerView()
I then assign this picker view to be the input view for the text field in viewDidLoad:
textField.inputView = subjectPickerView
So I'm not sure if this makes the picker view a subview of the scroll view, but it's just replacing the keyboard in this scenario.
Thanks to #AchmadJP's comment, I tried explicitly creating an equal widths constraint between my scroll view and my body view. This seems to have solved the issue.
The reason I had not done this previously was that the body view's leading space, trailing space, top space and bottom space were constrained to be the same as those of the scroll view. Theoretically, this should have meant that the widths were equal at all times, but apparently, that is not the case.
For anyone else with the same problem, you can see this answer for the solution.

scrolling/resizing UITableView

I'll get right to the point.
I have a UIViewController that has two subviews in it. The top one (let's call it HeaderView from now one) is a custom UIView and the bottom one is a UITableView.
I have set them up in InterfaceBuilder so that the HeaderView has 0 margin from the left, top and right, plus it has a fixed height.
The UITableView is directly underneath with 0 margin from all sides.
My goal is to achieve a behaviour such that when I start scrolling the UITableView's content the HeaderView will start shrinking and the UITableView becomes higher without scrolling. This should go on until the HeaderView has reached a minimum height. After that the UITableView should start scrolling as normal. When scrolling down the effect should be reversed.
I have initially started this out using a UIScrollView instead of the UITableView and I have achieved the desired result. Here is how:
connect the UIScrollView to the outlet
#IBOutlet weak var scrollView: UIScrollView!
set the UIScrollViewDelegate in the controller's viewDidLoad() method
self.scrollView.delegate = self
and declared the UIViewController to conform to the protocol
intercept when the UIScrollView scrolls:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.adjustScrolling(offset: scrollView.contentOffset.y, scrollView: scrollView)
}
in my adjustScrolling(offset:scrollView:) method the "magic" happens
Now let's look at what happens in this method.
private func adjustScrolling(offset: CGFloat, scrollView: UIScrollView) {
// bind value between 0 and max header scroll
let actualOffset: CGFloat = offset < 0 ? 0 : (offset >= self.maxHeaderScroll ? self.maxHeaderScroll : offset)
// avoid useless calculations
if (actualOffset == self.currentOffset) {
return
}
/**
* Apply the vertical scrolling to the header
*/
// Translate the header up to give more space to the scrollView
let headerTransform = CATransform3DTranslate(CATransform3DIdentity, 0, -(actualOffset), 0)
self.header.layer.transform = headerTransform
// Adjust header's subviews to new size
self.header.didScrollBy(actualOffset)
/**
* Apply the corrected vertical scrolling to the scrollView
*/
// Resize the scrollView to fill all empty space
let newScrollViewY = self.header.frame.origin.y + self.header.frame.height
scrollView.frame = CGRect(
x: 0,
y: newScrollViewY,
width: scrollView.frame.width,
height: scrollView.frame.height + (scrollView.frame.origin.y - newScrollViewY)
)
// Translate the scrollView's content view down to contrast scrolling
let scrollTransform = CATransform3DTranslate(CATransform3DIdentity, 0, (actualOffset), 0)
scrollView.subviews[0].layer.transform = scrollTransform
// Set bottom inset to show content hidden by translation
scrollView.contentInset = UIEdgeInsets(
top: 0,
left: 0,
bottom: actualOffset,
right: 0
)
self.currentOffset = actualOffset
}
If I haven't forgotten anything this should be enough to achieve the desired effect. Let me break it down:
I calculate the actualOffset binding it between 0 and self.MaxHeaderScroll which is just 67 (I think, it's calculated dynamically but this doesn't really matter)
If I see that the actualOffset hasn't changed since the last time this function was called I don't bother to aplly any changes. This avoids some useless calculations.
I apply the scrolling to the header by translating it up with a CATransform3DTranslate on just the y axis by negative actualOffset.
I call self.header.didScrollBy(actualOffset) so that the HeaderView can apply some visual changes internally. This doesn't concearn the question though.
I resize the scrollView so that it keeps 0 margin from top and bottom now that the HeaderView is higher up.
I translate down the scrollView's content by the same actualOffset amount to contrast the scrolling. This piece is essential to the correct visual effect that I want to achieve. If I didn't do this, the scrollView would still resize correctly but the content would start scrolling right away, which I don't want. It should only start scrolling once the HeaderView reaches it's minimum height.
I now set a bottom inset in the scrollView so that I am able to scroll it all the way to the end. Without this, the last part of the scrollView would be cut off since the scrollView itself would think it reached the end of it's content.
Lastly I store the actualOffset for later comparison
As I said, this works fine. The problem arises when I switch from a UIScrollView to a UITableView. I assumed it would work since UITableView inherits from UIScrollView.
The only piece of code that doesn't work is the number 6. I don't really know what is going wrong so I will just list everything I have found out and/or noticed. Hopefully someone will be able to help me out.
in the case of the UIScrollView, in point 6, the scrollView.subviews[0] refers to a view that holds all the content inside it. When I change to UITableView this subview seems to be of the type UITableViewWrapperView which I could not find any documentation about, nor does XCode recognize it as a valid class. This is already frustrating.
if in point 6 I also give some translation on the x axis (let's say of 50) I can see an initial very quick translation that is immediately brought back to 0. This only happens when the UITableView starts scrolling, it doesn't go on while scrolling.
I have tried changing the frame of the subview in point 6 to achieve the desired result. Although the scrolling is correct, the top cells start disappearing as I scroll the UITableView. I thin this is because I am using dequeueReusableCell(withIdentifier:for:) to instatiate the cells and the UITableView thinks that the top cells aren't visible when they actually are. I wasn't able to work around this problem.
I have tried setting the self.tableView.tableHeaderView to a UIView of the actualOffset height to contrast scrolling but this gave a weird effect where the cells would not scroll correctly and when the UITableView was brought back to the initial position, there would be a gap on top. No clue about this either.
I know there's a lot here so please don't hesitate asking for more details. Thank you in advance.
I made something like this recently, so heres how I achieved it:
Make a UIView with a height constraint constant and link this to your view/VC, have you UITableview constrained to the VC's view full screen behind the UIView.
Now set your UITableViews contentInset top to the starting height of your 'headerView' now, in the scrollViewDidScroll you adjust the constant until the height of the header is at its minimum.
Here is a demo
If you just run it, the blue area is your 'header' and the colored rows are just any cell. You can autolayout whatever you want in the blue area and it should auto size and everything

UITextView content offset changes after setting frame

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;
}

Resources