Get X position of Frame in ScrollView - ios

I have a CustomScrollView which holds a list of UILabels and scrolls through them (the subclassing is for automatic positioning of the views). In the class I have a list of these views stored as an NSMutableArray. I have found that when I ask for label.view.frame.origin after getting the label from the array, I always get {0, 0}. Because of this, the logic I use to scroll does not work (the scroll is done programmatic-ally). I am trying to have the UILabel scroll to the center of the frame of the CustomScrollView. Here is a code sample to show where I am having problems:
#interface CustomScrollView : UIScrollView
#property (nonatomic) NSMutableArray* labels; //This is the array of UILabels
#end
#implementation CustomScrollView
-(void)scrollToIndex:(int)index withAnimation:(bool)animated
{
UILabel *label = (UILabel*)self.labels[index];
CGRect rect = CGRectMake(label.frame.origin.x - ((self.frame.size.width - label.frame.size.width) / 2), 0, self.frame.size.width, self.frame.size.height);
[self scrollRectToVisible:rect animated:animated];
}
#end
TL:DR - label.frame.origin.x is returning me 0 and not what it's relative position is within the superview.
Bonus - Whenever I instantiate my custom scroll view, it automatically adds two subviews which I have no idea where the come from. I set the background color and turn off the scroll bars and scroll enabled.
Thanks in advance for the help.
Edit [Jun 25 11:38] - Turns out the rect I am creating to scroll is correct, but calling [self scrollRectToVisible:rect animated:animated] is just not working.
Edit [Jun 25 12:04] - Here is the method which I call every time I add a subview
-(UILabel*)lastLabel
{
if (self.labels.count == 0)
return nil;
return (UILabel*)self.labels.lastObject;
}
-(void)adjustContentSize
{
if (self.labels.count > 0)
{
float lastModifier = [self lastLabel].frame.origin.x + [self lastLabel].frame.size.width + ((self.frame.size.width - [self lastLabel].frame.size.width) / 2);
self.contentSize = CGSizeMake(MAX(lastModifier, self.contentSize.width), self.frame.size.height);
}
}

Try using the convertRect function:
CGRect positionOfLabelInScrollView = [self.scrollView convertRect:myLabel.frame toView:nil];
[self.scrollView setContentOffset:CGPointMake(0, topOfLabel - positionOfLabelInScrollView.origin.y + positionOfLabelInScrollView.size.height) animated:YES];
This should scroll your scrollView to display the label.

Related

Implementing Inneractive / Inneractive banner ads - ad positioning issue

I am trying to use the Inneractive Ad SDK 5+ in my application and I'd like to position the ad on the bottom. In every case I've tried it will always be in the middle of the screen, centred horizontally and vertically.
Code:
self.myAdView = [[IaAdView alloc]
initWithAppId:#"MyAppID_iPhone" adType:IaAdType_Banner delegate:self];
self.myAdView.adConfig.refreshIntervalInSec = 30;
[[InneractiveAdSDK sharedInstance] loadAd:self.myAdView];
Delegate method:
- (void)InneractiveAdLoaded:(IaAd *)adView {
NSLog(#"AD LOADED METHOD CALLED");
// Here I tried many things I found online without any effect
[self.view addSubview:self.myAdView];
}
Here how i implement the property at the top of the viewcontroller.m file:
#interface ViewController () <InneractiveAdDelegate>
#property (nonatomic, retain) IaAdView* myAdView;
#end
#implementation ViewController
#synthesize myAdView = _myAdView;
I am sure it must be just a tiny thing but I can't figure it out. I found many solutions that include initWithFrame but this is not usable in this case.
Any help would be appreciated.
Update 1:
I've tried:
- (void)InneractiveAdLoaded:(IaAd *)adView {
NSLog(#"AD LOADED METHOD CALLED");
self.myAdView.frame=CGRectMake(x,y,width,height); // With any number and random number
and
self.myAdView.frame=CGRectOffset(self.myAdView.frame, 0, yOffset); // With any number and random number
[self.view addSubview:self.myAdView];
}
Nothing helped. Ad stays absolutely centred. I am starting to think there is something wrong with the setup but besides the positioning everything is working fine. See photo below.
Update 2:
Tried the following without success. ios8 shows banner centred on screen and ios7 for some reason not at all once the line has been added.
- (void)InneractiveAdLoaded:(IaAd *)adView {
NSLog(#"AD LOADED METHOD CALLED");
self.myAdView.frame = CGRectMake(0, self.view.frame.size.height - self.myAdView.frame.size.height , self.view.frame.size.width, self.myAdView.frame.size.height);
[self.view addSubview:self.myAdView];
}
Here's a working solution:
You have two options here -
Wrapping your adView in the external UIView and then you have the full control
Updating the frame of adView on the 'InneractiveAdLoaded:' event
ANYWAY: adView must be added to it's superView before calling 'loadAd:' SDK method.
Let me know, if you have any questions.
Update:
Option 2:
#import "ViewController.h"
#import "InneractiveAdSDK.h"
#interface ViewController () <InneractiveAdDelegate>
#property (nonatomic, strong) IaAdView *adView;
#end
#implementation ViewController {}
- (void)viewDidLoad {
[super viewDidLoad];
self.adView = [[IaAdView alloc] initWithAppId:#"<Your ad unit ID>" adType:IaAdType_Banner delegate:self];
[self.view addSubview:self.adView];
[[InneractiveAdSDK sharedInstance] loadAd:self.adView];
}
- (void)positionAd {
const CGFloat x = (self.view.bounds.size.width - self.adView.frame.size.width) / 2.0f;
const CGFloat y = self.view.bounds.size.height - self.adView.frame.size.height;
self.adView.frame = CGRectMake(x, y, self.adView.frame.size.width, self.adView.frame.size.height);
}
#pragma mark - InneractiveAdDelegate
- (void)InneractiveAdLoaded:(IaAd *)adView {
[self positionAd];
}
- (void)InneractiveDefaultAdLoaded:(IaAd *)adView {
[self positionAd];
}
- (UIViewController *)viewControllerForPresentingModalView { return self; }
#end
Try setting its frame this way:
self.myAdView.frame=CGRectMake(x,y,width,height);
Where x,y,width,height is your desired position and size.
Or if its already positioned nicely by X coordinate, width and height you could use:
self.myAdView.frame=CGRectOffset(self.myAdView.frame, 0, yOffset);
Where yOffset is how much you want it moved on that coordinate. Be sure you calculate it using self.view.frame and not with exact number, so its on bottom on every device.
To position your banner at the bottom of your UIView you need to change its frame.
self.myAdView.frame = CGRectMake(x, y, width, height);
self.myAdView.frame = CGRectMake(0, self.view.frame.size.height - self.myAdView.frame.size.height , self.view.frame.size.width, self.myAdView.frame.size.height);
x - Set to origin of 0
y - Set origin to the UIView's height. But this will make it appear off the bottom of the screen. So, we must subtract the height of our self.myAdView to move it up onto the screen: self.view.frame.size.height - self.myAdView.frame.size.height
width - Set it to the width of the UIView: self.view.frame.size.width
height - Set it to the height of our self.myAdView: self.myAdView.frame.size.height

Changing a frame in viewDidLayoutSubviews

When a view controller's view is first shown I want to run an animation in which all elements in the view controller slide from outside the bottom of the screen to their natural positions. To achieve this, I do subview.frame.origin.y += self.view.frame.size.height in viewDidLayoutSubviews. I also tried viewWillAppear, but it doesn't work at all. Then I animate them up to their natural positions with subview.frame.origin.y -= self.view.frame.size.height in viewDidAppear.
The problem is that viewDidLayoutSubviews is called several times throughout the view controller's lifespan. As such, when things like showing the keyboard happen all my content gets replaced outside the view again.
Is there a better method for doing this? Do I need to add some sort of flag to check whether the animation has already run?
EDIT: here's the code. Here I'm calling prepareAppearance in viewDidLayoutSubviews, which works, but viewDidLayoutSubviews is called multiple times throughout the controller's life span.
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self prepareAppearance];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self animateAppearance];
}
- (NSArray *)animatableViews
{
return #[self.createAccountButton, self.facebookButton, self.linkedInButton, self.loginButton];
}
- (void)prepareAppearance
{
NSArray * views = [self animatableViews];
NSUInteger count = [views count];
for (NSUInteger it=0 ; it < count ; ++it) {
UIView * view = [views objectAtIndex:it];
CGRect frame = view.frame;
// Move the views outside the screen, to the bottom
frame.origin.y += self.view.frame.size.height;
[view setFrame:frame];
}
}
- (void)animateAppearance
{
NSArray * views = [self animatableViews];
NSUInteger count = [views count];
for (NSUInteger it=0 ; it < count ; ++it) {
__weak UIView * weakView = [views objectAtIndex:it];
CGRect referenceFrame = self.view.frame;
[UIView animateWithDuration:0.4f
delay:0.05f * it
options:UIViewAnimationOptionCurveEaseOut
animations:^{
CGRect frame = weakView.frame;
frame.origin.y -= referenceFrame.size.height;
[weakView setFrame:frame];
}
completion:^(BOOL finished) {
}];
}
}
If you need to animate something when view will appear and then not touch subviews later, I would suggest the following:
Don't change/touch viewDidLayoutSubviews
Add logic to move elements outside the screen (to their initial position before animation) in viewWillAppear
Add logic to animate elements into their proper position in viewDidAppear
UPDATE:
If you're using auto-layout (which is very good thing), you can't animate views by changing their frames directly (because auto-layout would ignore that and change them again). What you need to do is to expose outlets to constraints responsible for Y-position (in your case) and change that constraints rather then setting frames.
Also don't forget to include call to [weakView layoutIfNeeded] after you update constraints in the animation method.
I did both things in viewDidAppear:. It seems that when viewDidAppear: is called, the view is not actually visible, but about to. So the UI elements never show in their initial position if they are replaced there.

UITableView Not Scrolling after contentInset

i'm working on a project where i have a tableview and a uitextfield.
I'm applying the following method when the uitextfield gain/loose the focus :
-(void)enableInset {
CGFloat offSet = -30.0f;
UIEdgeInsets inset = UIEdgeInsetsMake(placesMapView.frame.size.height - offSet, 0.0f, 0.0f, 00.f);
// Updating the tableView position.
placesTableView.contentInset = inset;
placesTableView.contentOffset = CGPointMake(0.0f, -(placesMapView.frame.size.height - offSet));
placesTableView.scrollIndicatorInsets = inset;
}
and
- (void)disableInset {
CGFloat offset = self.navigationController.navigationBar.frame.size.height + [UIApplication sharedApplication].statusBarFrame.size.height;
UIEdgeInsets inset = UIEdgeInsetsMake(offset, 0.0f, 0.0f, 00.f);
placesTableView.contentInset = inset;
placesTableView.contentOffset = CGPointMake(0.0f, -offset);
placesTableView.scrollIndicatorInsets = inset;
}
The enableInset method is called in viewDidLayoutSubviews.
then when i call disableInset and enableInset, the UITableView can not be scrolled anymore.
What did i did wrong ? Any idea where i can look for some answer ?
EDIT :
If it can help, i added the project on github :
https://github.com/Loadex/PlaceViewer
To re-produce the bug :
Scroll the list, tap on the search bar, hit cancel, try to scroll again the list.
Weirdly click on the filter button, when the UIActionSheet is dismissed, the scroll is working again.
While looking for a solution to your problem i noticed that the bottom part of the contentInset of your placesTableView kept changing through the different states. It was 0 when in the initial state where you could see the map, and the tableView was behaving as expected. It got set to 216 when the keyboard came up after tapping the search field. I figured this was some automated communication between the tableView and the keyboard (through Notifications or something you did in PlacesQueryTableViewController). This is fine because we want the bottom inset to be set to the top of the keyboard when it appears. Now, here comes the buggy part. When I tapped the cancel button, the contentInset.bottom got set to -216.
I can't quite explain why this happens, but I suspect it has something to do with how that automatic change of the inset is implemented. I suspect that it does something like tableView.contentInset.bottom -= heightOfKeyboard, and that probably happens when the animation is finished, and not before. The source of your problem is that you change that bottom of contentInset before the animation is done, and thus before that automatic change has happened. So you're setting the bottom to 0 as soon as the user taps cancel. Then the system comes in and reduces it by the height of the keyboard, which turns out to be 216. That's what I think is happening anyway.
To fix this problem, avoid changing the bottom part of the contentInset and just change the top part. placesTableView.contentInset.top is readOnly, but if you do it like in the code below, you can get around that. I have just changed two lines of code in each method, the ones that have to do with the inset. Hopefully you see what I did.
-(void)enableInset {
NSLog(#"Enabling insets");
// Setting the tableView to overlay the map view
CGFloat offSet = [placestableViewController tableView:placestableViewController.tableView heightForRowAtIndexPath:nil] - 30.0f;
UIEdgeInsets inset = placesTableView.contentInset; // UIEdgeInsetsMake(placesMapView.frame.size.height - offSet, 0.0f, 0.0f, 0.0f);
inset.top = placesMapView.frame.size.height - offSet;
// Updating the tableView position.
placesTableView.contentInset = inset;
placesTableView.contentOffset = CGPointMake(0.0f, -(placesMapView.frame.size.height - offSet));
placesTableView.scrollIndicatorInsets = inset;
placesMapView.hidden = NO;
[placestableViewController loadObjects];}
- (void)disableInset {
NSLog(#"Disable insets");
CGFloat offset = self.navigationController.navigationBar.frame.size.height + [UIApplication sharedApplication].statusBarFrame.size.height;
UIEdgeInsets inset = placesTableView.contentInset;// UIEdgeInsetsMake(offset, 0.0f, 0.0f, 0.0f);
inset.top = offset;
placesTableView.contentInset = inset;
placesTableView.contentOffset = CGPointMake(0.0f, -offset);
placesTableView.scrollIndicatorInsets = inset;
// Hidding the map while in search
placesMapView.hidden = YES;}
.
BTW, if you want to know how I found the contentInset values at the different states, it's quite simple. What I did was to set myself as the delegate of placesTableView in - (void)viewDidLoad like this placesTableView.delegate = self;. I also had to change the #interfacestatement to #interface KrackMapViewController () <UITableViewDelegate> to say that we conform to the UITableViewDelegate. Now, here's the trick: UITableViewDelegate conforms to UIScrollViewDelegate. That means we can implement methods of the scroll view delegate. The one that is particularly interesting is this one:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
NSLog(#"Did scroll with insetTop: %f, insetBottom: %f, contentOffset: %f", placesTableView.contentInset.top,
placesTableView.contentInset.bottom,
placesTableView.contentOffset.y);
}
That lets us know when you start dragging the tableView, and in there simply NSLog out the different parameters.
I hope this was helpful. Let me know if this works for you.
After a few research here is what I noticed:
The TableView when the keyboard is released is not scrolling because the tableview seems to believe that it is displayed on the entire screen. I tried to add more data in the tableview and we can see that the view is scrolling a little.
What I believe happened is that when the keyboard is hidden, some automatic calls are done and messing with what I set in my enableInset method. Here is my working solution:
I registered for the hideKeyboard event:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidHide:)
name:UIKeyboardDidHideNotification
object:nil];
and in the callback I called enableInset:
- (void)keyboardDidHide: (NSNotification *) notif{
[self enableInset];
}
And the view is scrolling back again.
Any explanation about this are welcome.

UIView won't move programmatically

so there must be something simple I am missing because I just can't figure out how to move my UIView
This is what I have so far
my .m file
- (void)viewDidLoad
{
[super viewDidLoad];
[self buttonLayout];
}
-(void)buttonLayout
{
UIView *theView = [self buttonGroup];
theView.center=CGPointMake(50, 50);
}
My .h file
#property (nonatomic, retain) IBOutlet UIView *buttonGroup;
So this is what I have so far and i just cant seem to get it to move at all
P.S i don't want it animated as i'm getting my layout to move to compensate for different iPhone screen sizes :)
I have not tested this, but it seems like you need to:
A) Remove the view from its parent.
B) Adjust the center of its 'frame', not the view.
C) Re-add the subview.
Like so:
UIView *theView = [self buttonGroup];
CGRect theFrame = theView.frame;
[theView removeFromSuperview];
theFrame.origin.x = 50.0;
theFrame.origin.y = 50.0;
theView.frame = theFrame;
[self.view addSubview:theView];
This should work with autolayout.
As the other poster says, you can't move views around by changing their center or frame when you have auto-layout in effect.
Instead, you have to add a vertical and horizontal constraint that positions the view from the top/left edge, connect an outlet to the constraint, and then change the constant value on the constraint from code.
I had similar problem , but when i tried to change the origin as suggested in first solution , i got "Expression not assignable" error.
so i solved it with below solution in viewDidLoad().
First save its center in some temp variable , then change the centre and add again to its parent view.
CGPoint buttonCenter = self.yourButton.center;
[self.yourButton removeFromSuperview];
buttonCenter.y = buttonCenter.y + 50.0;
self.yourButton.center = buttonCenter;
[self.view addSubview:self.yourButton];

Horizontally center multiple UIViews

I want to horizontally center a number of UIViews (they happen to be circles) in the master UIView. It will end up basically looking like the dots on the standard Page Control.
I have all the code written to create the circle UIViews I just have no idea how to arrange them horizontally and dynamically at run time.
Essentially I need some kind of horizontal container where I can do this
-(void)addCircle{
[self addSubView:[CircleView init]];
}
And it will auto arrange however many children it has in the center.
I get confused with auto-layout as well from time to time but here is a way how you can do it programmatically: (I assume that you add your circle views to a containerView property of your view controller and you do not add any other views to it.)
Add these two properties to your view controller:
#property (nonatomic) CGRect circleViewFrame;
#property (nonatomic) CGFloat delta;
Initiate those properties with the desired values in your view controller's viewDidLoad method:
// the size (frame) of your circle views
self.circleViewFrame = CGRectMake(0, 0, 10, 10);
// the horizontal distance between your circle views
self.delta = 10.0;
Now we add your "automatic addCircle method":
- (void)addCircleView {
UIView *newCircleView = [self createCircleView];
[self.containerView addSubview:newCircleView];
[self alignCircleViews];
}
Of course we need to implement the createCircleView method...
- (UIView*)createCircleView {
// Create your circle view here - I use a simple square view as an example
UIView *circleView = [[UIView alloc] initWithFrame:self.circleViewFrame];
// Set the backgroundColor to some solid color so you can see the view :)
circleView.backgroundColor = [UIColor redColor];
return circleView;
}
... and the alignCircleViews method:
- (void)alignCircleViews {
int numberOfSubviews = [self.containerView.subviews count];
CGFloat totalWidth = (numberOfSubviews * self.circleViewFrame.size.width) + (numberOfSubviews - 1) * self.delta;
CGFloat x = (self.containerView.frame.size.width / 2) - (totalWidth / 2);
for (int i = 0; i < numberOfSubviews; i++) {
UIView *circleView = self.containerView.subviews[i];
circleView.frame = CGRectMake(x,
self.circleViewFrame.origin.y,
self.circleViewFrame.size.width,
self.circleViewFrame.size.height);
x += self.circleViewFrame.size.width + self.delta;
}
}
This is the most important method which will automatically realign all your subviews each time a new circleView is added. The result will look like this:
Simple steps: append circle to container view, resize container view, center align container view
-(void)addToContanerView:(CircleView*)circle{
circle.rect.frame = CGrectMake(containers_end,container_y,no_change,no_change);
[containerView addSubview:circle];
[containerView sizeToFit];
containerView.center = self.view.center;
}
Assumptions:
containers_end & containers_y you can get from CGRectMax function,
for UIView SizeToFit method check here
To take care of rotation use make sure your Autoresizing subviews are set for left, right bottom and top margin.
You can try using this library. I have used it on several of my projects and so far, it worked really well.
https://github.com/davamale/DMHorizontalView

Resources