UIScrollView contentLayoutGuide and zooming centered - uiscrollview

The problem to be solved here is how to zoom in a UIScrollView while staying centered. If you don't take some sort of precautions, the default is that as we zoom out, the zoomed view slides up to the top left corner of the scroll view, like this:
So how to prevent this, and keep the zoomed view in the center as we zoom? As you probably know, there are traditional ways of handling this by messing with the scroll view's layout, as described by Josh and Eliza in the brilliant classic WWDC video 104 from 2010. This can be done by using a delegate or by subclassing UIScrollView, and gives the desired result:
Now comes WWDC 2017 video 201 (https://developer.apple.com/videos/play/wwdc2017/201/?time=1496), and there's Eliza making a claim that the new (iOS 11) contentLayoutGuide solves the problem of zooming while staying centered in a new way: she says to center the content view at the center of the content layout guide.
But she doesn't demonstrate. And when I try it for myself, I find it isn't solving the problem. I'm zooming in just fine, but when zooming out, so that the zoom scale is smaller than 1, the content view moves up to the top left, just as it always has.
Has anyone figured out what this claim in the video actually means? How does iOS 11 make it easier to zoom centered than in the past?
EDIT I actually received a sample project from Apple in response to my bug report, which they claimed illustrated how to solve this, and it didn't! So I conclude that even Apple doesn't know what they're talking about here.

The view goes to the top left because the contentSize of the scroll view is not defined. When using the new Auto Layout guides in iOS 11, it's still necessary to define the contentSize.
Add the following constraints:
scrollView.contentLayoutGuide.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor),
scrollView.contentLayoutGuide.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor)
This worked for me, when I had a contentView with a fixed width/height and the following additional constraints:
// give the centerView explicit height and width constraints
centerView.widthAnchor.constraint(equalToConstant: 500),
centerView.heightAnchor.constraint(equalToConstant: 500),
// pin the center of the centerView to the center of the scrollView's contentLayoutGuide
centerView.centerXAnchor.constraint(equalTo: scrollView.contentLayoutGuide.centerXAnchor),
centerView.centerYAnchor.constraint(equalTo: scrollView.contentLayoutGuide.centerYAnchor)

This is the solution you are / everybody is looking for. In my case I want to center a view inside a table view scroll view. So if the table view scrolls the custom view will always be in the center of the scroll view content.
// create a view
let v:UIView = UIView(frame:CGRect.zero) // use zero if using constraints
ibTableView.addSubview(v)
ibTableView.bringSubview(toFront:v)
v.translatesAutoresizingMaskIntoConstraints = no
v.backgroundColor = .yellow
v.widthAnchor.constraint(equalToConstant:100).isActive = yes
v.heightAnchor.constraint(equalToConstant:100).isActive = yes
// set scrollview guides
ibTableView.contentLayoutGuide.widthAnchor.constraint(equalTo:ibTableView.frameLayoutGuide.widthAnchor).isActive = yes
ibTableView.contentLayoutGuide.heightAnchor.constraint(equalTo:ibTableView.frameLayoutGuide.heightAnchor).isActive = yes
// anchor view
v.centerXAnchor.constraint(equalTo:ibTableView.contentLayoutGuide.centerXAnchor).isActive = yes
v.centerYAnchor.constraint(equalTo:ibTableView.contentLayoutGuide.centerYAnchor).isActive = yes

Related

tvOS: Rounded corners for image view when focused

I have an image view that will get this awesome tvOS focus effect when the containing view gets focused.
The problem is - it should have rounded corners. Now this is easily done:
imageView.layer.cornerRadius = 5
imageView.layer.masksToBounds = true
I have to set either masksToBounds of the layer or clipsToBounds of the image view to true (which is basically the same), in order to clip the edges of the image - but as soon as I do this, the focus effect won't work any more, because it will get clipped as well.
I had more or less the same problem with buttons, but since the focus effect is much simpler than for the image view (only scaling and shadow), I just implemented it myself, but that is not an option for the image view, with all the effects applied (moving, shimmering, and so on...)
Is there an easier way? Did I miss something? I can't be the only trying to figure out how this works!? :)
I have found out an alternative solution. What one may do is to actually draw the image, clipping out the corners with an alpha channel. The image then gets scaled correctly when focused. That applied to the layer. Then, to have the alpha channel added to the other layers (like the one for the glowing effect) we need to set; "masksFocusEffectToContents = true".
I made an extension for it, based on this answer:
Swift 4.2
extension UIImageView {
func roundedImage(corners: UIRectCorner, radius: CGFloat) {
let rect = CGRect(origin:CGPoint(x: 0, y: 0), size: self.frame.size)
UIGraphicsBeginImageContextWithOptions(self.frame.size, false, 1)
UIBezierPath(
roundedRect: rect,
byRoundingCorners: corners,
cornerRadii: CGSize(width: radius, height: radius)
).addClip()
self.draw(rect)
self.image = UIGraphicsGetImageFromCurrentImageContext()!
// Shadows - Change shadowOpacity to value > 0 to enable the shadows
self.layer.shadowOpacity = 0
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 10, height: 15)
self.layer.shadowRadius = 3
// This propagate the transparency to the the overlay layers,
// like the one for the glowing effect.
self.masksFocusEffectToContents = true
}
}
Then to apply the rounded corners call:
myImageView.adjustsImageWhenAncestorFocused = true
myImageView.clipToBounds = false
// masks all corners with a radius of 25 in myImageView
myImageView.roundedImage(corners: UIRectCorner.allCorners, radius: 25)
One can obviously modify roundedImage() to add the parameters to define the shadows at the calling time.
Downsides:
Borders behave like cornerRadius (they get drawn inside the image).
But I think I made it working somewhere, then investigating further I
lost the changes
I am not exactly sure this is the right way to do it. I am quite confident there must be some methods out there doing it in a couple of lines. In tvOS 11 Apple introduced the round badges (animatable and all), shown at WWDC 2017. I just can't find a sample for them.
Otherwise, tvOS 12 (beta for now) introduced the Lockup. I managed to implement them programmatically, as shown in this answer.
https://forums.developer.apple.com/thread/20513
We are also facing this same issue. When you round the corners, you can see the "shine" still has a rectange shape.
I showed the issue to the Dev Evangelists at the Tech Talks in Toronto and they said it's a bug. It's reported and open rdar://23846376
For 2022.
Note that you can just use UICardView on tvOS for the effect.
Simply put the UIImageView inside the card view.
Don't forget to actually turn OFF "adjust on ancestor focus" and "user interaction enabled" on the image view, or else it will "doubly expand" when the card view expands!
There's also a weird issue where you have to add 20 to the height of all card views to make them work neatly with and enclosed image view.

Vertical NSLevelIndicator OSX

I wonder if there is a way of creating/modifying a NSLevelIndicator object so it can be positioned vertically, i.e. display discrete levels from bottom up, not from left to right, so it can be also used as element of interface-building library in Xcode?
There are lots of examples of such level displays in Apple and non-Apple OSX applications, and quite a few reasons why such an object should exist, yet how to create such an object for some reason (from what I can see in developer forums) seems either not worth asking or a "best kept secret".
Is there a template code which can be modified to into an object of such properties?
I haven't even faintest idea if such an object should really be written from scratch? Mission impossible?
Thanks in advance!
Try using
[NSView setFrameRotation:90];
it's sketchy but easier than a custom view
Edit: Alternatively try
[levelView setFrameCenterRotation:90];
SetFrameRotation:90 rotated it around the bottom left axis for me so it ended up being clipped. This one rotates it around the centre so you should be able to see it. I just made a quick swift playground showcasing it: http://cl.ly/WsL8/Vertical%20LevelIndicatorView.playground.zip
Edit again: If you're still stuck, I made a sample project with a vertical level indicator in objective-c: http://cl.ly/WrdH/levelindicator.zip
Swift 5.5.1 on macOS 11.6
myLevelIndicator.frameRotation = 90
If you need to reposition the indicator to fit within the view, realize the center of rotation is the origin of the level indicator.
So, to set the rotated level indicator 20px in from the left of the view, compute that for the new frame origin of the level indicator, not forgetting to adjust for the indicators height when it is horizontal because the original height will affect the final position when rotated.
myLevelIndicator.frame.origin = CGPoint(x: self.view.frame.minX+20+myLevelIndicator.frame.height, y: myLevelIndicator.frame.minY)
Of course, this can be avoided by placing the control in the correct position to allow for rotation within IB if that works for you. Some may not be using IB and creating these controls programmatically.

How can I draw something on top of AVPlayer video in Cocoa?

In my app I want to be able to draw rectangles on top of the playing video. I already implemented the drawing canvas which is a NSView, and I have a player implemented as well.
However, when I place my drawing canvas on top of my player view, player view is still showing up on top.
I was thinking that my drawing canvas wasn't on top of the player view, but it is. When I change the size of the window, I can see that on the area of the window that is not covered by the video I can draw. However, magically my drawing is not showing up on top of the playing video.
I really don't know where to find the problem.
Any help is highly appreciated!
i also had a problem where one NSView that i add to a superView was layer backed and the last NSView wasn't, and always the layer backed NSView would be on top, so i'd recommend you to enable the layer for any NSView that would come on top of the AVPlayer,
[yourCanvasView setWantsLayer : YES];
...
i think this would do the job,
There's a solution for iOS, as answered here, and it works in OS X too (I tested on OS X 10.10.2).
Basically, set the zPosition of the view you want on top:
topView.layer.zPosition = 1;

How to do this simple thing with XCode's new Auto Layout feature?

Im trying to do something that seems like it should be a no-brainer. But alas, im too dumb to figure this one out. So i need help.
I have an iphone simulator (with a vertical orientation).
The main view has a subview (a blue UIView) that should be displayed in the bottom 25% of the screen. The final rendered screen would basically look like a white screen with the bottom section being blue.
I want the edges of the subview to hug the screen edge. But thats not the part I'm struggling with. The tricky thing is configuring the height of this subview (and the layout of its contents, if any) when I suddenly rotate the orientation of the simulator. If i do this, then everything looks odd.
I would also like to note that what I see in the Interface Builder (with Auto Layout enabled) looks nothing like the app when it loads in the simulator.
PS - for now i will keep playing around with this. maybe i will eventually reach a eureka moment.
* Updated with screen shots *
This is what my layout looks like in Interface Builder:
And this is what it ends up looking like in the simulator:
* Update 2 *
Ok. After updating my post with the screenshots, it dawned on me that the simulator doesn't look like the 4" screen version (even though it does on my computer screen). After running the 4" simulator i can see the the layout just like the IB layout. But this makes me wonder why auto-layout wasn't "smart" enough to adjust for the smaller 3.5" screen.
It looks to me like you have a fixed height constraint from the top edge of the blue view to the superview that is pushing content down - conversely you may have a fixed height view above it that is smooshing things. Try pinning the top edge of the blue view to the superview and lowering the priority or changing it from an equality to a less than equal to constraint.
In IB in XCode you can change the canvas size for iPhone layouts, look for the single button on the right hand side that looks like a rectangle with arrows above and below, or inside it. In the screenshot below it is the leftmost button.
You could simply turn off Auto Layout. Springs and struts can readily make your subview 25% of the superview's height.
Or, you can certainly stick with it, but you'll need to adjust the constraints in code. There's no way to establish such a constraint in the current version of Xcode. Set constraints in IB to pin the box to the bottom of the window and fix the height, setting an outlet for the height constraint. Then in -viewDidLoad, remove the height constraint and add a new constraint to the superview which expresses the height relationship you want.
This isn't to discourage you from learning Auto Layout, but to point out that it's a bit complicated, and that the constraint tools in IB are unintuitive and relatively weak. The WWDC video Introduction to Auto Layout is a good starting point.

Set margins of view xcode

I have been programming android apps for a bit, and I am now making an iphone app. I want to make margins for my view. I would not like to explain my exact situation, but if someone helps me with this I'll be able to figure out what I need to do.
I have two views, I want the first view to take up the entire screen. Then I want another view to always be, lets say 20 pixels from the edge of the screen on all four sides. Is there a simple way to do that in xcode?
Thanks
I assume you're using Interface Builder (now part of Xcode).
Add the view as you suggest - leaving a 20 pixel border around all 4 sides. Set all 6 resize options (flexible height, width, top, bottom, left & right).
Ensure that 'autoresize subviews' is enabled on the parent view.
The view will now resize if the parent view also resizes, leaving a 20px margin as required.

Resources