NSScrollview not scrolling programmatically? - objective-c

Note:Both Horizontal and vertical scrollers are visible on the screen and work fine.But I cant make them move Programatically.
I am working on a cocoa desktop application.I am using the NSScrollview in my Mainmenu.xib file and I am creating an outlet in its owner which is Appdelegate.h .
Here is the outlet
#property(nonatomic,retain) IBOutlet NSScrollView* scrollview;
When I try to set a new referencing outlet of my NSScrollview from the interface builder and take the line to file's owner I only see one option "delegate".I dont see the outlet scrollview.
So I connect the scrollview to the delegate in file's owner (As I cant see the scrollview outlet).
Now I am trying to do auto scrolling in my code.Here is the code
for(int a=0;a<10000;a++)
{
NSPoint pointToScrollTo = NSMakePoint ( 100+a,100+a ); // Any point you like.
[[self.scrollview contentView] scrollToPoint: pointToScrollTo];
[self.scrollview reflectScrolledClipView: [self.scrollview contentView]];
}
This code does not work and The scroller does not scroll automatically.
I am trying to make a slow scrolling animation with this code.

NSClipView has a scrollToPoint: method which can be used to scroll programmatically:
- (IBAction)scrollToMid:(id)sender
{
CGFloat midYPoint = [self.scrollView contentView].frame.size.height/2.0;
[[self.scrollView contentView] scrollToPoint:NSMakePoint(0.0, midYPoint)];
[self.scrollView reflectScrolledClipView:[self.scrollView contentView]];
}
If you want animated scrolling, you have to set the boundsOrigin via animator proxy. (Because neither NSScrollView nor NSClipView expose an animatable scroll point property)
- (IBAction)scrollToMidAnimated:(id)sender
{
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:2.0];
NSClipView* clipView = [self.scrollView contentView];
NSPoint newOrigin = [clipView bounds].origin;
newOrigin.y = [self.scrollView contentView].frame.size.height/2.0;
[[clipView animator] setBoundsOrigin:newOrigin];
[NSAnimationContext endGrouping];
}

Better way to get this, flip the view. I have worked on this to get the scroll view to top.
-(void)scrollToTop:(NSScrollView *)scrollView
{
NSPoint newScrollOrigin;
if ([[scrollView documentView] isFlipped]) {
newScrollOrigin=NSMakePoint(0.0,0.0);
} else {
newScrollOrigin=NSMakePoint(0.0,NSMaxY([[scrollView documentView] frame]) -NSHeight([[scrollView contentView] bounds]));
}
[[scrollView documentView] scrollPoint:newScrollOrigin];
}
That should be called in windowDidLoad to get the position .
-(void)windowDidLoad
{
[super windowDidLoad];
[self scrollToTop:_myScroll];
}

In the identity inspector of your File's Owner, check its class. It must be set to NSApplication. You have to change it to AppDelegate to get the outlet scrollview to which you can connect your scrollview.
But the better option is to have a NSObject in your xib and set its class to AppDelegate. (However xcode create it by default, check if a item "AppDelegate" is present in the box "objects" below "Placeholders" where FileOwner is present). Now drag a Referencing outlet line from your scrollview to that object. It will show the IBOutlet object name of your Appdelegate class.
UPDATE:
You cannot create a slow animation using a for loop. The for loop is so fast that you will see only the final result, not the complete scrolling. You need to use core animation for slow animation or NSTimer to delay scrolling to make it slow.
Plus please try using [scrollview documentView] , everywhere in place of [scrollview contentView] . Although i haven't given it a try.
Ok I worked on it, and this is the final result
If your scroll bar is on top, the following will move it to end:
[scroll.contentView scrollToPoint:NSMakePoint(0, ((NSView*)scroll.contentView).frame.size.height - scroll.contentSize.height)];
[scroll reflectScrolledClipView: [scroll contentView]];
You may modify the above as per your need.
Also even your code is working, but you need to take the relative points in place of any point.
So instead of placing your code in the for loop, test it by dragging a button on your xib, and in its button action write:
NSPoint pointToScrollTo = NSMakePoint ( 100,100 ); // Any point you like.
[[scrollview contentView] scrollToPoint: pointToScrollTo];
[scrollview reflectScrolledClipView: [scrollview contentView]];
Now run the application, set the scroll of scrollview to some random position. Then click the button and check if scroll moved somewhere.
If it works then your code is fine, your only need to set the points accordingly. It worked for me.

I found that if you are using NSViewController, you need to do the 'scrollPoint' in your viewWillAppear method, rather than in viewWillLoad. FYI.

Related

Adding subviews in NSWindow while resizing the windows, the UI gets jumbled

I have a window and a subview is added, this subview is the container of another view(accessed and added using NSViewController).
I have disabled autolayout and doing resizing from springs. The subviews gets resize correctly on window resize.
If I add / remove subviews keeping the window size same it works fine. But if I add subview and maximize it and then remove and then add, it gets jumbled.
Some time it happens straight forward as :
Open the main window (it opens in small size). Maximize it, then add the subview, the subview is added to its original size as drawn in xib. Expected behavior is the subview should get expand and cover the main window.
I am not able to find the solution. Please help me to fix this. The sample code and sample project is attached here.
//In AppDelegate
- (IBAction)buttonClicked:(id)sender {
if (!self.myVC) {
self.myVC = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
}
[self.containerView addSubview:self.myVC.view];
}
- (IBAction)clearClicked:(id)sender {
for (NSView *view in self.containerView.subviews) {
[view removeFromSuperview];
}
}
I got the answer,
Before adding the view to container view, I get the container's rect and set the frame for child view.
- (IBAction)buttonClicked:(id)sender {
if (!self.myVC) {
self.myVC = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
}
NSRect rect = NSMakeRect(0, 0, self.containerView.frame.size.width, self.containerView.frame.size.height);
[self.myVC.view setFrame:rect];
[self.containerView addSubview:self.myVC.view];
}

Animate NSButton resizing when modifying imagePosition property

I have an NSButton subclass in an autolayout environment. The subclass implements mouseEntered: and mouseExited: methods, which should change the positioning of the button's image. On mouseEntered: it is changed to NSImageLeft (showing the title) and on mouseExited: it is changed to NSImageOnly (not showing the title).
Autolayout takes care of the resizing of the NSButton subclass in my view hierarchy, but it does not look smooth as it is not using CoreAnimation for resizing the button width according to whether the title is to be shown or not. How can I let the resizing happen with an animation?
Open an animation group, enable implicit animations, change the button image position, and then force layout:
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context){
context.allowsImplicitAnimation = YES;
button.imagePosition = NSImageOnly;
[button.window layoutIfNeeded];
} completionHandler:nil];
I solved it by using the following methods:
- (void)mouseEntered:(NSEvent *)theEvent {
NSSize titleStringSize = [self.title sizeWithAttributes:#{NSFontAttributeName:[NSFont boldSystemFontOfSize:11.0]}];
self.widthConstraint.animator.constant = titleStringSize.width+self.originalWidth+5.0;
}
- (void)mouseExited:(NSEvent *)theEvent {
self.widthConstraint.animator.constant = self.originalWidth;
}
originalWidth and widthConstraint are properties of my NSButton subclass that are defined during awakeFromNib.

UIButton inside UIView doesn't respond to touch events

I've put a UIButton inside a custom UIView and the button is not receiving any touch events (it doesn't get into the highlighted state, so my problem is not about being unable to wire up a touch inside up handler). I've tried both putting it into the XIB in Interface Builder, and also tried programatically adding the UIButton into the UIView seperately, both ended with no luck. All my views are inside a UIScrollView, so I first though UIScrollView may be blocking them, so I've also added a button programatically exactly the same way I add my custom view into UIScrollView, and the button worked, elimination the possibility of UIScrollView could be the cause. My View's hiearchy is like this:
The button is over the image view, and the front layer isn't occupying my button completely, so there's no reason for me not be physically interacting with the button. At my custom view's code side, I'm creating my view as such:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
UIView *sub = [[[NSBundle mainBundle] loadNibNamed:#"ProfileView" owner:self options:nil] objectAtIndex:0];
[self addSubview:sub];
[sub setUserInteractionEnabled:YES];
[self setUserInteractionEnabled:YES];
CALayer *layer = sub.layer;
layer.masksToBounds = YES;
layer.borderWidth = 5.0;
layer.borderColor = [UIColor whiteColor].CGColor;
layer.cornerRadius = 30.0;
/*layer.shadowOffset = CGSizeZero;
layer.shadowRadius = 20.0;
layer.shadowColor = [[UIColor blackColor] CGColor];
layer.shadowOpacity = 0.8;
*/
}
return self;
}
I've tried all combinations of setUserInteractionsEnabled, and had no luck. (Yes, also set them to checked in Interface Builder too). I've also read in another question with a similar problem that I should try overriding 'canBecomeFirstResponder' to return 'YES' and I've also done that too. But the problem persists, I can't click the button. I've not given any special properties, settings to the button, it's just a regular one. My other objects in the view (labels below, image view behind the button etc.) are working properly without problems. What could be possibly wrong here?
Thanks,
Can.
UPDATE: Here is a quick reproduction of the problem: https://dl.dropbox.com/u/79632924/Test.zip
Try to run and click the button.
Looking at the test project, I believe your problem in the way you create TestView, you do not specify the frame for it, so basically the parent view is 0 size, and the subviews you see from XIB extending out of the parent view and thus do not get anything in responder chain.
You should either specify the frame when creating TestView, or adjust the frame after loading XIB file.
I have had this problem as well. The cause for me was that the UIButton superview frame was of height 0, so I believe that even though a touch was happening, it was not being passed down to the button.
After making sure that the button's superview took a larger rectangle as a frame the button actions worked.
The root cause for this problem on my side was a faulty auto layout implementation (I forgot to set the height constraint for the button's superview).
I've found the solution. I was initializing my custom view as:
MyView *view = [[MyView alloc] init];
I've initialized it instead with a frame of my view's size, and it started responding to events:
CGRect rect = CGRectMake(0,0,width,height);
MyView *view = [[MyView alloc] initWithFrame:rect];
Storyboard Solution
Just for anyone wanting a solution to this when using storyboards and constraints.
Add a constraint between the superview (containing the button) and the UIButton with an equal heights constraint.
In my case, I had selected embed UIButton in a UIView with no inset on the storyboard. Adding the additional height constraint between the UIButton and the superview allowed the UIButton to respond to touches.
You can confirm the issue by starting the View Debugger and visually confirm that the superview of the UIButton is not selectable.
(Xcode 11, *- Should also work in earlier versions)

UIScrollView does not work with EXC_BAD_ACCESS

I have a scrollview that is subview of view, and has the subviews. The problem is this: the scrollView came with the black background (as I have set transparent) and also does not work. The scrollView is connected with an IBOutlet. I redid the XIB 2 times, what needs fixing? When I add the scrollView as subview of view:
[self.view addSubview:self.scrollView];
I get this error during runtime:
0x132b61: calll 0x132b66; CA::Layer::ensure_transaction_recursively(CA::Transaction*) + 14
EXC_BAD_ACCESS(code=2 address=0xbf7ffffc)
If I do not add it as a subview in the code, the view controller opens and the scrollview is black and does not scroll.
You are probably doing somewhere, something like:
[myScrollView addSubview:myAnotherView];
[myAnotherView addSubview:myScrollView];
which kicks an unwanted recursion. Check your code.
Check if you init with your scrollView with frame:
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 280, 360)];
Remember also to set contentSize bigger than frame, for example:
self.scrollView.contentSize = CGSizeMake(2*280, 360);
Also add delegate in your interface:
<UIScrollViewDelegate>
And delegate it:
self.scrollView.delegate = self;
In my situation I had a UIView that was receiving the same error. In my case I had forgotten to create an IBOutlet for my view. Once I did this, the error went away.

How to make detail view scrollable in master detail app

I have only been working in iOS for a few months but I have been banging my head against the wall for hours and hours for something that seems like it should be pretty straightforward. I used the master detail template in Xcode for an iPad app. I want the detail portion to be scrollable to show content below what is visible in the frame, in either orientation. I have tried numerous combinations of adding scrollviews in the DetailViewController in viewWillAppear, viewDidLoad, loadView...and the best I can come up with is what looks like a scrollable view on the top layer as it does show scroll bars and shows me that I did the scrollView.contentSize correctly as I can pan around, but the actual view with the fields and stuff doesn't move and the fields are unable to be edited. Here is my viewDidAppear as it stands at the moment. As you can see in the NSLogs I am trying to understand the view stack. If I uncomment the line before the logs, I lose the scroll bars altogether.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 768, 1024)];
scrollView.contentSize = CGSizeMake(2048, 2048);
UIView *parentView = [[UIView alloc] init];
parentView = [[self view] superview];
[[parentView superview] addSubview:scrollView];
//[scrollView addSubview:[self view]];
NSLog(#"%#", [parentView superview]);
NSLog(#"%#", parentView);
NSLog(#"%#", [super view]);
NSLog(#"%#", [self view]);
[scrollView setDelegate:self];
}
I would sincerely appreciate any guidance or tips on how to properly implement UIScrollView for this scenario.
You should add the UIScrollView in IB. Be sure to move all of your existing views and controls to be subviews of the scroll view. And you'll have to link the scroll view to an IBOutlet so you can set the content size in your view controller.
Instead of trying to wrap a uiscrollview around your main view's superview (which you have incorrectly tried to take ownership of (it should be NULL anyways)), why not herd your UI elements into a full-sized subview and wrap the scrollview around that? It would be much much easier.
Your hierarchy would look like this:
Self.view -> ScrollView (with contentSize set) -> UIView containing elements that need to be scrolled -> various UI elements