Any way to change the clipping rect of the current context in UIView.drawRect? - uiview

I have a custom class which overrides the point(inside point: CGPoint, with event: UIEvent?) -> Bool function so that small buttons can have a touchable area greater than their frame. I thought it would be convenient if I could draw this touchable area around the button when working in Interface Builder. I overrode drawRect to fill the touchable area, but it seems that drawRect clips to the view's frame. Is there any way to change the clipping area to something bigger? I'd rather not add a child view for something like this.

Related

Is a view's frame always set by the time `draw(_:)` is called?

I use the below code to curve the edge of a custom view.
/// Curves `self's` trailing edge.
override func draw(_ rect: CGRect) {
super.draw(rect)
let path=UIBezierPath(ovalIn: .init( // The path defining `self's` curved edge.
x: rect.width - Self.ovalWidth, // Align the oval's trailing edge with `self's`.
y: (rect.height - Self.ovalHeight) / 2, // Center oval vertically within `self`.
width: Self.ovalWidth,
height: Self.ovalHeight
))
path.stroke() // Draws the border of the curved edge.
self.layer.mask={ $0.path=path.cgPath; return $0 }(CAShapeLayer())
}
My concern is that as far as I can see there is no guarantee that rect will be equal to self.frame; which would result in undefined behavior as far as drawing the mask goes. Is it possible that if I use self.frame instead of rect that draw(_:) could be called while self.frame has yet to be set, or has a value that is not up-to-date? — this view is laid out using AutoLayout.
Your concerns are only half correct. According to the docs:
The portion of the view’s bounds that needs to be updated. The first
time your view is drawn, this rectangle is typically the entire
visible bounds of your view. However, during subsequent drawing
operations, the rectangle may specify only part of your view.
Also in the docs:
You should limit any drawing to the rectangle specified in the rect
parameter
draw(_:) is typically only called when the view is first displayed (not when it is added as a subview) and when setNeedsDisplay() or setNeedsDisplay(_:) is called.
As far as I know there are only two causes for concern. The first is that your view is displayed before all of you constraints are set. The second is if you call setNeedsDisplay(_:) with a specific rectangle.

How to scroll UIPageViewController on top of UIPageViewController?

I have this really strange scenario. There's a UIPageViewController on top of UIPageViewController. And then the upper one is on the section where it's clear view, I would like to be able to swipe and use the UIPageViewController that is underneath. Sliding through pages on upper UIPageViewController is done with menu buttons.
It's something like :
[x][x][x][x]
[x][ ][x]
I've tried using
func point(inside point: CGPoint, with event: UIEvent?) -> Bool {}
But I cant seem to differentiate between what to return...
Any ideas, if there's any other way?
EDIT:

Issue with horizontal scrollview inside a vertical scrollview in ios swift

My layout is currently like this:
View
-- View
-- Vertical ScrollView
------ View
--------- Horizontal Paginated ScrollView
--------- View
------------- Horizontal ScrollView -- not working properly
See this image for view hierarchy screenshot from xcode:
Using Swift.
I am adding subviews dynamically to this "Size Select Scroll View"
Two Issues:
After adding views, there is no margin between the subviews. Each subview's coord. are like this: (10.0, 0.0, 44.0, 44.0), (54.0, 0.0, 44.0, 44.0), (98.0, 0.0, 44.0, 44.0), (142.0, 0.0, 44.0, 44.0) etc.
But the appearance is like this without the 10 points gap between each subview: http://i.stack.imgur.com/mCGRW.png
Scrolling horizontally is a pain. It only works on maybe 1/4 height from top of the scrollview area and very difficult to scroll. How do i layout subviews so that this scrollview is properly scrollable?
Note: I am explicitly setting content size of size scrollview to more than required so that i can see the scrolling.
I found out the issue. According to the Apple Docs the touch events will be passed to a subview only if it lies entirely in its parent.
In my case, the scrollview was going out of bounds of its parent view ( Details View), because of which touch events were weird. I increased the parent view's size to fit the scrollview and it works fine now.
From the docs (https://developer.apple.com/library/content/qa/qa2013/qa1812.html):
The most common cause of this problem is because your view is located outside the bounds of its parent view. When your application receives a touch event, a hit-testing process is initiated to determine which view should receive the event. The process starts with the root of the view hierarchy, typically the application's window, and searches through the subviews in front to back order until it finds the frontmost view under the touch. That view becomes the hit-test view and receives the touch event. Each view involved in this process first tests if the event location is within its bounds. Only after the test is successful, does the view pass the event to the subviews for further hit-testing. So if your view is under the touch but located outside its parent view's bounds, the parent view will fail to test the event location and won't pass the touch event to your view.
This is always a challenge in iOS.
There are various solutions which unfortunately depend on the exact situation.
Here's a drop-in solution which is often the right solution.
/*
So, PASS any touch to the NEXT view, BUT ALSO if any of OUR
subviews are buttons, etc, then THOSE should ALSO work normally. :/
*/
import UIKit
class Passthrough: UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return subviews.contains(where: {
!$0.isHidden
&& $0.isUserInteractionEnabled
&& $0.point(inside: self.convert(point, to: $0), with: event)
})
}
}
(Of course, you can also just drop the call in to some class, eg
class SomeListOrWhatever: UICollectionView, .. {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
print("MIGHT AS WELL TRY THIS")
return subviews.contains(where: {
!$0.isHidden
&& $0.isUserInteractionEnabled
&& $0.point(inside: self.convert(point, to: $0), with: event)
})
}
Even if you "don't totally understand what the problem is", this is "one of" the solutions!
For example, this is (usually!) the precise solution to the exact issue quoted from the doco by #kishorer747
It's definitely a real nuisance in iOS.

UIScrollView with Horizontal Zoom - Bezier Paths Drawn within Subviews Are Blurry

I have a scroll view that zooms only horizontally on pinch (based on this answer)
The content view has several children that are evenly spaced horizontally, and placed at different heights. Basically, I'm plotting a graph with dots.
Each marker is a custom UIView subclass that has a label as subview and draws a red circle inside drawRect(_:).
(The scroll view and its only child the content view use Autolayout, but all subviews of the content view are placed with frames calculated at runtime; no constraints except the label positioning respect to the marker)
I have modified the answer linked above, so that -when zooming- the dots get more spaced horizontally, but stay the same size.
This is part of the code for my content view:
override var transform: CGAffineTransform {
get {
return super.transform
}
set {
if let unzoomedViewHeight = unzoomedViewHeight {
// ^Initial height of the content view, captured
// on layoutSubviews() and used to calculate a
// a zoom transform that preserves the height
// (see linked answer for details)
// 1. Ignore vertical zooming for this view:
var modified = newValue
modified.d = 1.0
modified.ty = (1.0 - modified.a) * unzoomedViewHeight/2
super.transform = modified
// 2. Ignore zooming altogether in marker subviews:
var markerTransform = CGAffineTransformInvert(newValue)
markerTransform.d = 1.0 // No y-scale
for subview in subviews where subview is MarkerView {
subview.transform = markerTransform
}
}
}
}
Next, I want to connect my dots with straight line segments. The problem is, when I zoom the scroll view the segments become blurry.
In addition to the blurring, and because I am only zooming horizontally, the segments "shear" a bit (depending on their their slope), and thus the line width becomes uneven:
(Right now I am placing an intermediate view between each pair of dots, that draws a single segment - but the result is the same with a single path)
I have tried making the segment-drawing views aware of the transform that is applied to them by their parent, revert it, and instead modify their frame accordingly (and redraw the path within the new bounds); however it doesn't seem to work.
What is the best way to draw zoom-resistant bezier paths inside a scroll view?
Update:
I got rid of the pixelation by following this answer to a similar question (almost duplicate?); in my case the code translates to:
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat)
{
for segment in contentView.subviews where segment is SegmentView {
segment.layer.contentsScale = scale
segment.setNeedsDisplay()
}
}
...However, I still have the uneven line width issue due to stretching the (diagonal) segments only in the horizontal direction.
My initial thought, having not done this before exactly, is to store the graph as a bezier path rather than as a set of view line and point views, and based on a single unit axis. You could convert your data into this form just for drawing.
So, all points on the bezier path are normalised into the range 0 to 1.
Once you've done that you can apply a transform to the bezier path to translate (move) and scale (zoom) to the part you want and then draw the bezier path at full resolution.
This will work and is different to your current situation because your current code draws the views and then scales then so you get artifacts. The above option scales the path and then draws it.

How to set image only for a certain part of title in UIButton

I have a UIButton that has a title.
Consider it as Thu 20.
There is an image which is set as the background for the button,this image is width resized and content mode is aspect fit.So it appears to the full button.
My problem:
I need the image only for the "20" part and not the entire "Thu 20".
if you see the iPad calendar you will get the point.
How can i do it?
Thanks
Unless something like an attributed string has a functionality of putting a rectangle around some text I suggest you to create a custom view that handles this:
You would need a view that excepts 2 strings, a normal one and the one in the rectangle. Then insert a normal label, set the text, call sizeToFit. Then add the second label and do the same as first but in the end place it right next to the first label. Then add the image view onto the label respecting the labels dimensions. Then resize the whole view to fit all the views on it. Now you can add this custom label to a button or anywhere you want it...
I would rather suggest you place an Image VIew containing your Image and Then place a Label on top of Image with your "20" over it and then Place a custom UIButton with No backGround Color, and No Images on the top of both
With a bit of work you could override one of these UIButton methods to place the image at the correct location. Using a method within UILabel to determine the coordinates of "20" would give the the location for the image.
class UIButton {
// these return the rectangle for the background (assumes bounds), the content (image + title) and for the image and title separately. the content rect is calculated based
// on the title and image size and padding and then adjusted based on the control content alignment. there are no draw methods since the contents
// are rendered in separate subviews (UIImageView, UILabel)
open func backgroundRect(forBounds bounds: CGRect) -> CGRect
open func contentRect(forBounds bounds: CGRect) -> CGRect
open func titleRect(forContentRect contentRect: CGRect) -> CGRect
open func imageRect(forContentRect contentRect: CGRect) -> CGRect
}
See Apple's Documentation under "Getting Dimensions" for more information about these methods.

Resources