I have two UIimageview images which are draggable using touchesBegan and touchesMoved however they can be dragged anywhere on the screen without any limitations. I am trying to limit the UIimageview to be only draggable inside the UIimage (certain area).
Any idea what is the best approach?
Thanks
Use CGRectContainsPoint to limit the area.
For example:
let point = touch.location(in: self) // or elsewhere
if CGRectContainsPoint(image.frame, point) {
// could drag
} else {
// stop dragging
}
Related
I have a UIView with a PageViewController and 4 UIButtons as subviews. The PageViewController and the button subviews are independent from each other. (PageViewController scrolls across 4 pages while the UIButtons remain constant)
The problem I'm facing is that the buttons take quite a bit of space and I'd like to use this scroll gesture anywhere on the UIView including the areas with buttons.
If the user taps on the button, its respective action would occur but I'd like if I can START a scroll/swipe gesture on a button and have the PageViewControllers scroll feature work.
I've tried/searched several ways including using UIScrollView instead of a PageViewController. Any help would be greatly appreciated. Thanks in advance. :)
Just add your own gesture recogniser and watch for gestures which velocity is greater in the x direction and going from left to right.
override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if (gestureRecognizer.isMemberOfClass(UIPanGestureRecognizer)){
let point:CGPoint = (gestureRecognizer as UIPanGestureRecognizer).velocityInView(self)
if (abs(point.x) > abs(point.y)){
return true
}
}
}
Handle the gesture over here:
func handlePanGestureRecognizer(gesture: UIPanGestureRecognizer){
let translation:CGPoint = gesture.translationInView(self)
if(translation.x > 0){
//its going from left to right...
//...transition to the previous page
}else{
//...transition to the next page
}
Using the Interface Builder, I created a view with a UIScrollView in it.
I programmaticly add the buttons to the empty UIScrollView.
When the orientation changes, I use
- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
to call a method that resets the buttons on screen.
After that, the uiScrollView-content gets a new size using setContentSize.
No matter the width of the ContentSize, I can only interact (scroll/or tap a button) on the first 320px of the screen - which is the screen width in portrait mode.
When I set the contentSize-width to 2000, I can scroll to the left, but only with my fingers on the first 320 px instead of the full 480 (using a 3.5 inch iPhone).
What am I missing?
You need to resize the scrollview's frame, not just the content size (in fact, often you don't need to resize the content size, as the content may not change following an orientation change, but just the frame).
I had the same problem with a Popup (UIView) that was called from a (UIViewController).
In the UIViewController the popup was created as followed
func createPopup() {
let myPopup = MyPopup(frame: UIScreen.mainScreen().bounds)
self.view.addSubview(myPopup)
}
In the Popup (UIView) I needed to override layoutSubviews as follows:
override func layoutSubviews() {
super.layoutSubviews()
self.setNeedsDisplay()
let mainScreenBounds = UIScreen.mainScreen().bounds
view.frame = mainScreenBounds
super.frame = mainScreenBounds <-- Need to update the parent
** perform additional rotation/orientation code here **
}
I'm trying to move a UIView - "A" (Subview) inside another UIView - "B" (SuperView) using the touches moved method. I'm able to move the UIView outside the superview. I want to restrict the UIView inside the Superview bounds. Is there any way there can be a generic method to test if the subview is inside the visible rect of the superview ???
It sounds like you want to constrain the movement of the subview (viewA) to be always completely contained by the superview (viewB). CGRectContainsRect is the right answer, but it must be applied carefully, since a subview frame is specified in it's superview's coordinate system.
// inside touches moved, compute the newViewAFrame based on the movement
// but only assign it if it meets the containment constraint:
if (CGRectContainsRect(viewB.bounds, newViewAFrame)) {
viewA.frame = newViewAFrame;
}
Notice that we don't mention viewB.frame in the check. viewB's position in it's parent is not relevant to whether viewB contains viewA.
Use clipsToBounds method or CGRectContainsRect
youSuperView.clipsToBounds = YES;
I think it will be helpful to you
I had the same problem and this post helped me a lot, so I'll share my answer (that seems to fit exactly what you need) :
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if (SuperView.frame.contains(self.frame)) {
let oldCenter = self.center
self.center = touch.location(in: SuperView)
if(!SuperView.frame.contains(self.frame)) {
self.center = oldCenter
}
}
}
}
quite simple and you can use this in the methods touchesMoved and touchesEnded too, so it won't stop you when your UIView reach the limits (this is why oldCenter exists).
As mentioned by #dahn, if your view is INSIDE the SuperView, you must take care, because the coordinates of the first will be restrict to the frame of the second, so it may fail if the SuperView is not a full-screen view.
If it is not a full-screen view, the SuperView cannot be the dad of the SubView, because it will cause bugs. The solution is to keep the SuperView and the SubView both inside a third View (so their coordinate system will be the same and it will work fine).
Hope that helps someone someday :)
Swift 2.2:
let rect1 = CGRect(...)
let rect2 = CGRect(...)
There is a method to check this - CGRectContainsRect(rect1, rect2)
Is there a way I can set my scrollview not to clip its contents? (Which is a NSTextView)
I have a subclass of NSScrollView and want its content not to be clipped to its bounds.
I have tried overriding:
- (BOOL) wantsDefaultClipping{
return NO;
}
in MyScrollView and in MytextView without any effect.
In the iOS I would simply would do: myuitextView.clipsToBounds=NO; how can I do this in Cocoa?
EDIT
This is an example of what I want to achieve but in the mac
The scrollview is white, the scroller will never go outside its bounds but the text does since I did myuitextView.clipsToBounds=NO
See picture here
EDIT2
I wouldn't mind clip my view like #Josh suggested. But the real behaviour I would like to have can be explained with this picture:
Do you see the word *****EDIT***** that has being cut in the very first line?
I want the text not to be cut this way, rather I want it to completely appear and I will put a semitransparent image so it looks like it fades off when it's outside the frame.
Q: Why don't I simply put a semitransparent NSImageView on it so it looks like what I want?
A: Because 1.Scroller will be faded as well. Even if I correctly place the semitransparent NSImageView so the scroller looks fine, the cursor/caret will be able to go underneath the semitransparent NSImageView again it does not look good.
I would like to be able to control the area is clipped by NSClipView. I think that would solve my problem. Is there any alternative I have? maybe I can control the caret position or scrolling position through NSTextView so caret will never go near the top/bottom frame limits? or any work-around?
Any advice is appreciated.
Now that it's 2016 and we're using vibrant titlebars with full size content views, I'll add my thoughts to how someone might accomplish this. Hopefully, this will help anyone who came here looking for help on this, as it helped me.
This answers the question in regards to scrolling under the titlebar, but you could easily modify this technique to scroll under other things using the insets and caret position.
To get a scroll view (with or without an NSTextView inside of it) to scroll behind a titlebar, you can use:
// For transparent title.
window.titlebarAppearsTransparent = true
window.styleMask = window.styleMask | NSFullSizeContentViewWindowMask
window.appearance = NSAppearance(named: NSAppearanceNameVibrantLight)
This effectively overlays the titlebar of the NSWindow onto the window's contentView.
To constrain something to the top of the window without knowing the height of the titlebar:
// Make a constraint for SOMEVIEW to the top layout guide of the window:
let topEdgeConstraint = NSLayoutConstraint(
item: SOMEVIEW, attribute: NSLayoutAttribute.Top,
relatedBy: NSLayoutRelation.Equal,
toItem: window.contentLayoutGuide,
attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 0.0)
// Turn the constraint on automatically:
topEdgeConstraint.active = true
This allows you to constrain the top of an element to the bottom of the titlebar (and or toolbar + any accessory views it may have). This was shown at WWDC in 2015: https://developer.apple.com/videos/play/wwdc2014/220/
To get the scrollview to scroll under the titlebar but show its scrollbars inside the unobscured part of the window, pin it to the top of the content view in IB or via code, which will cause it to be under the titlebar. Then, tell it to automatically update it's insets:
scrollView.automaticallyAdjustsContentInsets = true
Finally, you can subclass your window and handle the cursor/caret position. There is a presumed bug (or developer error on my part) that doesn't make the scrollview always scroll to the cursor/caret when it goes above or below the content insets of the scrollview.
To fix this, you must manually find the caret position and scroll to see it when the selection changes. Forgive my awful code, but it seems to get the job done. This code belongs in an NSWindow subclass, so self is referring to the window.
// MARK: NSTextViewDelegate
func textViewDidChangeSelection(notification: NSNotification) {
scrollIfCaretIsObscured()
textView.needsDisplay = true // Prevents a selection rendering glitch from sticking around
}
// MARK: My Scrolling Functions
func scrollIfCaretIsObscured() {
let rect = caretRectInWindow()
let y: CGFloat = caretYPositionInWindow() - rect.height
// Todo: Make this consider the text view's ruler height, if present:
let tbHeight: CGFloat
if textView.rulerVisible {
// Ruler is shown:
tbHeight = (try! titlebarHeight()) + textViewRulerHeight
} else {
// Ruler is hidden
tbHeight = try! titlebarHeight()
}
if y <= tbHeight {
scrollToCursor()
}
}
func caretYPositionInWindow() -> CGFloat {
let caretRectInWin: NSRect = caretRectInWindow()
let caretYPosInWin: CGFloat = self.contentView!.frame.height - caretRectInWin.origin.y
return caretYPosInWin
}
func caretRectInWindow() -> CGRect {
// My own version of something based off of an old, outdated
// answer on stack overflow.
// Credit: http://stackoverflow.com/questions/6948914/nspopover-below-caret-in-nstextview
let caretRect: NSRect = textView.firstRectForCharacterRange(textView.selectedRange(), actualRange: nil)
let caretRectInWin: NSRect = self.convertRectFromScreen(caretRect)
return caretRectInWin
}
/// Scrolls to the current caret position inside the text view.
/// - Parameter textView: The specified text view to work with.
func scrollToCursor() {
let caretRectInScreenCoords = textView.firstRectForCharacterRange(textView.selectedRange(), actualRange: nil)
let caretRectInWindowCoords = self.convertRectFromScreen(caretRectInScreenCoords)
let caretRectInTextView = textView.convertRect(caretRectInWindowCoords, fromView: nil)
textView.scrollRectToVisible(caretRectInTextView)
}
enum WindowErrors: ErrorType {
case CannotFindTitlebarHeight
}
/// Calculates the combined height of the titlebar and toolbar.
/// Don't try this at home.
func titlebarHeight() throws -> CGFloat {
// Try the official way first:
if self.titlebarAccessoryViewControllers.count > 0 {
let textViewInspectorBar = self.titlebarAccessoryViewControllers[0].view
if let titlebarAccessoryClipView = textViewInspectorBar.superview {
if let view = titlebarAccessoryClipView.superview {
if let titleBarView = view.superview {
let titleBarHeight: CGFloat = titleBarView.frame.height
return titleBarHeight
}
}
}
}
throw WindowErrors.CannotFindTitlebarHeight
}
Hope this helps!
I would simply try to observe the document view's frame and match the scroll view's frame when the document resizes.
This is a little hairy. AFAIK, NSViews can't draw outside their own frame. At any rate I've never seen it done, and I was somewhat surprised when I realized that UIView allows it by default. But what you probably want to do here is not manipulate clipping rectangles (doing any such thing inside NSScrollView will probably not do what you want or expect), but instead try to cover up the vertically-truncated text lines with either layers or views that are the same color as the background. Perhaps you could subclass NSClipView and override viewBoundsChanged: and/or viewFrameChanged: in order to notice when the text view is being shifted, and adjust your "shades" accordingly.
You might consider using a translucent layer to achieve this appearance, without actually drawing outside your view. I'm not certain of the rules on iOS, but on the Mac, a view drawing outside its bounds can cause interference with surrounding drawing.
However, you can set the clipping region to be whatever you like inside your scroll view subclass's drawRect: using -[NSBezierPath setClip:]:
- (void)drawRect:(NSRect)dirtyRect {
[NSGraphicsContext saveGraphicsState];
[[NSBezierPath bezierPathWithRect:[[self documentView] frame]] setClip];
//...
[NSGraphicsContext restoreGraphicsState];
}
It might be possible (since you asked) to use this code in an NSClipView subclass, but there's not much info about that, and I think you may have a hard time making it interact properly with its scroll view. If it were me, I'd try subclassing NSScrollView first.
I have a UIScrollView that contains a custom UIView. Inside the custom UIView, I'd like to know the rectangle in which it's visible (i.e. not clipped).
The quick-n-dirty solution is to have the custom UIView assume that the parent is a UIScrollView and get the content size through it, but I'm looking for a better solution that doesn't involve make such assumptions.
This should do the trick
CGRect visibleRect = CGRectIntersection(self.frame, superview.bounds);
Use that in the UIView and it should get you the rectangle (if any) that represents the visible section of that view in it's superview (The UIScrollView). I'm assuming here that there is no view between them in the hierarchy, but if there is, fiddling the code should be trivial.
Hope I could help!
It would help if you would give more info on what is that you are trying to accomplish.
If you want to know the size of the super view you can do this:
CGRect superFrame = [self superview].frame;
Swift 3
extension UIView {
var visibleRect: CGRect? {
guard let superview = superview else { return nil }
return frame.intersection(superview.bounds)
}
}