Calculating left UIBarButtonItem width - objective-c

I found out the hard way UIBarButtonItem frame width is 0. After googling, i found a method to traverse the nav controllers subviews to find the left bar button's width. It works but it just seems like there has to be a cleaner way to do this.
There would be no reason my app would get rejected for using this method right?
UIView *leftBarButtonView = nil;
for (UIView* v in self.navigationController.navigationBar.subviews) {
if ([[v class].description isEqualToString:#"UINavigationButton"]) {
if (leftBarButtonView==nil) {
if (v.frame.origin.x > 0.0)
leftBarButtonView = v;
} else if (v.frame.origin.x < leftBarButtonView.frame.origin.x && v.frame.origin.x>0.0) {
leftBarButtonView = v; // this view is further right
}
}
}

No, it won't probably get rejected - probably, as nobody except Apple can say it for sure. However, one thing is sure:
[[v class].description isEqualToString:#"UINavigationButton"]
won't work. Use
[v isKindOfClass:objc_getClass("UINavigationButton")]
instead.

Related

Added whitespace in UIWebview - removing UIWebView whitespace in iOS7 & iOS8

Im loading local html files, since iOS7 there is added white space on top in the UIWebView.(I cant post an image as i do not have enough points.)
image can be seen here- snap shot from iPhone simulator, uiwebview surrounded by black frame, the html content is grey, but there is white added above it
I have tried to adjust the zoom using
[webView stringByEvaluatingJavaScriptFromString:#"document. body.style.zoom = 5.0;"];
webView.scalesPageToFit = NO;
credit to: Srikar Appal
I also set tried to remove white spacing:
NSString *padding = #"document.body.style.margin='0';document.body.style.padding = '0'";
[webView stringByEvaluatingJavaScriptFromString:padding];
credit to: thenextmillionaire
still no luck. In the desktop chrome browser there is no whitespace. The html files are Google Swiffy files - containing html and JSON.
edit: updated Image
Try self.automaticallyAdjustsScrollViewInsets = NO; in ViewDidLoad.
ios 7 add 64px automatically for scroll view. (status bar and nav bar)
This problem only affects the UIWebView if it is the first subview of the parent view. One alternative way to work around this problem is to add another non-visible empty view to the parent view as the first view. In Interface Builder add a zero size subview and use the Editor->Arrange->Send to Back menu command.
If you're not using Interface Builder, but instead are subclassing the UIWebView, then it can be done by creating a UIView instance variable called scrollFixView and overriding the following methods:
- (void)didMoveToSuperview
{
[super didMoveToSuperview];
if ([self superview].subviews.firstObject == self) {
_scrollFixView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
_scrollFixView.hidden = YES;
[[self superview] insertSubview:_scrollFixView belowSubview:self];
}
}
- (void)removeFromSuperview
{
if (_scrollFixView) {
[_scrollFixView removeFromSuperview];
_scrollFixView = nil;
}
[super removeFromSuperview];
}
I had the same problem so I tried a few things:-)
This worked for me, but correct me please if there is a better way.
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
if(self.navigationController.navigationBar.translucent == YES)
{
_webView.scrollView.contentOffset = CGPointMake(_webView.frame.origin.x, _webView.frame.origin.y - 54);
}
}
So basically you need to :
1) Add the UIWebView delegate method - webViewDidFinishLoad:
2) Then I setup an if statement to check if the translucent option is active.
The last one you only need to do of course if you give the user the option within your app.
The number after the _webView.frame.origin.y is just for my app. It may differ for you.
I solved this problem by simply setting a constraint on the WebView, setting the top space between it and the View top to 0, causing the NavBar to overlap the whitespace.
One alternative to Jeff Kranenburg's method is to subclass and override the UIWebView subclasses' UIScrollViewDelegate method scrollViewDidScroll:. This is only appropriate if scrolling is turned off for your subclass.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if ([[self superclass] instancesRespondToSelector:_cmd]) {
[super scrollViewDidScroll:scrollView];
}
[self fixUpScrollViewContentOffset];
}
- (void)fixUpScrollViewContentOffset
{
if (!CGPointEqualToPoint(self.scrollView.contentOffset, CGPointZero)) {
self.scrollView.contentOffset = CGPointZero;
}
}
I already got it .
here my logic code, When the application open the website you must get the size of your webview then set it on height
here my code
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) webpage.getLayoutParams();
p.height = webpage.getHeight();
// check if how long you need to set your height for webpage then set it :)
Log.e(" webpage.getHeight()", String.valueOf(webpage.getHeight()));
webpage.setLayoutParams(p);
Hope you will take my code and my answer to :) works on any devices even tabs too :)

UICollectionView Floating Headers on Top and Side

How do you implement headers in a UICollectionView? I know you can put in supplementary views, but I don't know how to make them "float" above a section like headers in a UITableView do.
Here's my situation: I have a collectionView with the cells laid out in a grid format. You can scroll horizontally and vertically. I want to have a header on top so that when you scroll horizontally it scrolls horizontally with it, but it doesn't scroll vertically. I also want the same sort of thing on the left side where it scrolls vertically with the collectionView but not horizontally. Is implementing this with supplementary views the right approach?
The final functionality I am looking for is similar to that of the Numbers spreadsheet app on iOS.
Thanks in advance for your help!
Update for iOS9:
let flow = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
flow.sectionHeadersPinToVisibleBounds = true
Pass it along.
EDIT: As mentioned in the comments, this answer is valid, but not for multiple sections. See this blog for a better solution: http://blog.radi.ws/post/32905838158/sticky-headers-for-uicollectionview-using#notes
You need to specify that behaviour in your layout:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray* attributesArray = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
BOOL headerVisible = NO;
for (UICollectionViewLayoutAttributes *attributes in attributesArray) {
if ([attributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
headerVisible = YES;
attributes.frame = CGRectMake(self.collectionView.contentOffset.x, 0, self.headerReferenceSize.width, self.headerReferenceSize.height);
attributes.alpha = HEADER_ALPHA;
attributes.zIndex = 2;
}
}
if (!headerVisible) {
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader
atIndexPath:[NSIndexPath
indexPathForItem:0
inSection:0]];
[attributesArray addObject:attributes];
}
return attributesArray;
}

Detecting UIScrollView Position

I am trying to use a UIScrollView as a navigation device in my iOS application. Basically, I want the application to load specific content, based on the position of a paging-enabled UIScrollView. It is sort of like a navigation wheel.
Currently, I have a two-page (2*320) scrollview, and I am using the scrollViewDidEndDragging delegate method and contentoffset.x to load the appropriate content. I try to determine the position of the scrollview as such:
if (scrollView.contentOffset.x < 320) {
// Load content 1
}
else if (scrollView.contentOffset.x >= 320) {
// Load content 2
}
My problem is that I either do not understand how to work with contentoffset or it is not suitable to deal with my problem. Either way, I am stuck. Can someone let me know where I went wrong and/or show me a more efficient way to determine scrollview position?
I am stupid, I forgot to mention what the problem is... Basically, I cannot track the position of the scrollview. When I move to the second page it only registers changes if I go over the 620 limit (bounce rightward). But when I go back to the starting position (page 1), it does register the changes. In other words, my tracking is not accurate.
Delegate and everything else is working fine, I log them!
It's hard to say because you never specifically mentioned what the problem is, but I'll take a couple guesses at it.
If the delegate method isn't being called at all you need to remember to set the scroll views delegate:
[myScrollView setDelegate:self];
Otherwise, the problem with using scrollViewDidEndDragging to detect the scroll views offset is that it will check the offset at the point where you stop dragging which could be within the destination pages rect, or within the starting pages rect. As an alternative, I'd suggest you use scrollViewDidEndDecelerating to check the content offset as it will be called as soon as the scroll view comes to a stop at its destination page.
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (scrollView.contentOffset.x < 320) {
NSLog(#"%#",NSStringFromCGPoint(scrollView.contentOffset));
}
else if (scrollView.contentOffset.x >= 320) {
NSLog(#"%#",NSStringFromCGPoint(scrollView.contentOffset));
}
}
I think this could help you :). With scrollViewDidScroll you always get the exact position of your scrollView:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.x < 320)
{
// Load content 1
}
else if (scrollView.contentOffset.x >= 320)
{
// Load content 2
}
}
NSLog(#"Position: %g", scrollView.contentOffset.x);
Do not forget the delegate UIScrollViewDelegate in your .h file.
Here a solution for getting the current position (page) as integer Value:
Use scrollViewDidEndDecelerating: methode:
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSLog(#"%i", (int)(_scrollView.contentOffset.x / _scrollView.frame.size.width));
}
If for any reason, you'd like to be notified before the view ended decelerating; for example, to perform animations. You can also use this:
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// Where the scroll will end (vertically)
let offSetY = targetContentOffset.pointee.y
// I've got a scroll view with paging enabled so I use this to check which "page" is selected.
if offSetY == firstPageView.frame.origin.y {
// Do something…
} else if offSetY == secondPageView.frame.origin.y {
// Do something…
}
}
Using UIScrollViewDelegate,
func scrollViewWillBeginDragging(scrollView: UIScrollView){
print("dragging begins")
print("Position: \(scrollView.contentOffset.x) , \(scrollView.contentOffset.y) ")
}

How do you move the legal sign in mapview

I wonder if anyone know how you move the legal sign on a mapview, right now my toolbar is covering it. Does anyone know how? There is lot's of help with the google logo but nothing on the apple maps.
In Swift:
mapView.layoutMargins = UIEdgeInsetsMake(top, right, -20, left)
I tested this in OS9 and it works.
Swift 5.2
// -20 will make the legal disclaimer move down. If you want the
// disclaimer to move up, use a positive number.
mapView.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: -20, right: 0)
This should work, although I'm not sure whether Apple will allow you to do that
UILabel *attributionLabel = [mapView.subviews objectAtIndex:1];
attributionLabel.center = CGPointMake(attributionLabel.center.x, attributionLabel.center.y - 44.0f);
This is still possible in iOS 7, but only (?) if placed in viewDidAppear.
The coords are reset if placed in viewDidLoad or viewWillAppear.
UILabel *attributionLabel = [mapView.subviews objectAtIndex:1];
attributionLabel.center = CGPointMake(attributionLabel.center.x, attributionLabel.center.y - 44.0f);
These methods no longer work on iOS 7. Correct way is to specify bottomLayoutGuide on your UIViewController.
Described in detail here
Changing the position doesn't quite work, however hiding the "Legal" button works perfectly.
[[mapView.subviews objectAtIndex:1] setHidden:YES]
EDIT:
Swift 2.0 iOS equivalent
mapView.subviews[1].isHidden = true
Carrying on Skeet Skeet point .
I implemented your approach it worked well but after coming in the viewcontroller multiple times legal label y keeps on decreasing as you see the logic it always displaces itself. so instead changing centre i propose
we change frame
UILabel *attributionLabel = [mapView.subviews objectAtIndex:1];
attributionLabel.frame = CGRectMake(20, self.view.frame.size.height - 135, attributionLabel.frame.size.width, attributionLabel.frame.size.height);
\\135 is height of your bottom view that was hiding legal
I wrote extension that worked for me. It can be used in animation block to animate those changes:
import MapKit
extension MKMapView {
/// Workaround for layoutMargins bug.
func setLegalInsets(left: CGFloat, bottom: CGFloat) {
let oldLeft = layoutMargins.left
let oldBottom = layoutMargins.bottom
let lblLegal = (subviews.filter { view in
return view is UILabel
}).first
lblLegal?.frame.origin.x += left - oldLeft
lblLegal?.frame.origin.y -= bottom - oldBottom
layoutMargins.left = left
layoutMargins.bottom = bottom
}
}
#Dymtro's answer works well for me, but I would suggest checking the size of the subviews first. This should at least prevent possible crashes if the view hierarchy changes in the future:
override func viewWillLayoutSubviews() {
positionLegalMapLabel()
}
func positionLegalMapLabel() {
if self.mapView.subviews.count > 1 {
let legalMapLabel = self.mapView.subviews[1]
legalMapLabel.frame.origin = CGPointMake(self.mapView.bounds.size.width - legalMapLabel.frame.size.width - 7, legalMapLabel.frame.origin.y)
}
}
Swift 4+
You can change the position of those by setting the layoutMargins of the mapView.
For example this will push it off from the bottom:
mapView.layoutMargins.bottom = -100
Also you can change edge insets you need all at once:
mapView.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: -100, right: 0)
A Swift 3 example based on #xeieshan's example that works when compiled against iOS 10 SDK. In my example I have a transparent bar in the bottom that animates up when the map view is being present. The label repositioning can also be animated.
// reposition the 'Legal' label above the transparent bottom bar
// unfortunately there is no safe way to identify the label but it is the last subview - hopefully this will not change 😱
if let legalLabel = mapView.subviews.last {
var frame = legalLabel.frame
frame.origin.y = frame.origin.y - self.bottomBar.bounds.size.height // reposition it above the bottom bar
legalLabel.frame = frame
}
Use viewWillLayoutSubviews() instead of viewDidAppear() to avoid a jump.
override func viewWillLayoutSubviews() {
positionLegalMapLabel()
}
func positionLegalMapLabel() {
let legalMapLabel = self.mapView.subviews[1]
legalMapLabel.frame.origin = CGPointMake(self.mapView.bounds.size.width - legalMapLabel.frame.size.width - 7, legalMapLabel.frame.origin.y)
}

Change NSView subviews order

I'm working at a custom control that presents different handles. I want to be sure that the selected handle has a Z-index grater then the others handles.
Is there a way to swap view order?
I found this function sortSubviewsUsingFunction:context: but i can't understand whether this is the right solution.
Cocoa introduced a new API in macOS 10.0.
It's similar to iOS, you can pass another view to be displayed below or above.
[self.view addSubview:myView positioned:NSWindowBelow relativeTo:myViewInBackground];
Checkout the documentation; in my experience NSWindowBelow and NSWindowAbove seemed reversed though.
It is pretty simple, you can use a function that compare 2 subviews to reorder them. Here a simple solution based on view's tag:
[mainView sortSubviewsUsingFunction:(NSComparisonResult (*)(id, id, void*))compareViews context:nil];
...
NSComparisonResult compareViews(id firstView, id secondView, void *context) {
int firstTag = [firstView tag];
int secondTag = [secondView tag];
if (firstTag == secondTag) {
return NSOrderedSame;
} else {
if (firstTag < secondTag) {
return NSOrderedAscending;
} else {
return NSOrderedDescending;
}
}
}
Yes, sortSubviewsUsingFunction:context: can do what you're looking to do, however it might be overkill if all you'd like is for one particular view to sit on top of the (unordered) others. In that case, you should look into the bringSubviewToFront: method of UIView. That way, you could do something like this in your IBAction for bringing up the handle:
[self.view bringSubviewToFront: handle];
Assuming that handle is an object that is/inherits from UIView. If this isn't what you're looking for, then by all means, go with sortSubviewsUsingFunction:context:, in which case you'll need to alter the individual tag properties for each subview, and sort accordingly.
As i understand from you question you can loop all you view and add them to array then you have the option to add them to you view with index .. and you have the following functions.
[self.view insertSubview:view1 atIndex:2]
[self.view insertSubview:view1 aboveSubview:0]
[self.view insertSubview:view1 belowSubview:1]
then you can swap it's order as you need..