I have two views
One is container of AVPlayerLayer like this
containerView.layer.addSublayer(playerLayer)
and containerView has own auto layout about superview
self.view.addSubview(containerView)
containerView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
containerView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
containerView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
containerView.heightAnchor.constraint(equalToConstant: 300).isActive = true
so i override viewDidLayoutSubviews()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
playerLayer.frame = containerView.frame
}
and second view is custom playback control view on containerView
containerView.addSubview(playBackControlView)
playBackControlView.leftAnchor.constraint(equalTo: containerView.leftAnchor).isActive = true
playBackControlView.rightAnchor.constraint(equalTo: containerView.rightAnchor).isActive = true
playBackControlView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
playBackControlView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
and there are several views like UIButton, UISlider etc
Here is my question
when i tapped one of buttons on playBackControlView which called enlargeScreenButton how can make my containerView, playBackControlView and AVPlayerLayer gonna be a full screen like landscape mode at once
I read this question already but only playerLayer changed, not whole view
(I will not device rotation)
I did many thing on Stackoverflow but they were fit on my case
if i transform some views are broken and if i change frame with UIScreen.main.bounds also it broke some views
I'd suggest you create a PlayerView subclass with custom layer view as such:
class PlayerView: UIView
override class var layerClass: AnyClass {
return AVPlayerLayer.self
}
}
This way you'll be able to manipulate the view without caring about the sublayer and it's animation. Then you'd be able to use the animations similar to ones from the answer you mentioned, but on the view. With all the subviews added using auto layout - you should be good to go and they should change accordingly when you animate using UIView.animate
Related
Problem
I have a custom UIView that has an image and selection (border) subview. I want to be able to add this custom UIView as a subview of a larger blank view. Here's the catch, the larger blank view needs to clip all of the subviews to its bounds (clipToBounds). However, the user can select one of the custom UIViews within the large blank view, where the subview is then highlighted by a border.
The problem is that because the large blank view clips to bounds, the outline for the selected subview is cut off.
I want the image in the subview to clip to the bounds of the large blank view, but still be able to see the full selection outline of the subview (which is cut off due to the large blank view's corner radius.
I am using UIKit and Swift
👎 What I Currently Have:
👍 What I Want:
The image part of the subview clips to the bounds (corner radius) of the large blank view, but the outline selection view in the subview should not.
Thanks in advance for all your help!
I think what you are looking for is not technically possible as defined by the docs
From the docs:
clipsToBounds
Setting this value to true causes subviews to be clipped to the bounds of the receiver. If set to false, subviews whose frames extend beyond the visible bounds of the receiver are not clipped. The default value is false.
So the subviews do not have control of whether they get clipped or not, it's the container view that decides.
So I believe Matic's answer is right in that the structure he proposes gives you the most flexibility.
With that being said, here are a couple of work arounds I can think of:
First, set up to recreated your scenario
Custom UIView
// Simple custom UIView with image view and selection UIView
fileprivate class CustomBorderView: UIView
{
private var isSelected = false
{
willSet
{
toggleBorder(newValue)
}
}
var imageView = UIImageView()
var selectionView = UIView()
init()
{
super.init(frame: CGRect.zero)
configureImageView()
configureSelectionView()
}
required init?(coder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews()
{
super.layoutSubviews()
}
private func configureImageView()
{
imageView.image = UIImage(named: "image-test")
imageView.contentMode = .scaleAspectFill
addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: topAnchor).isActive = true
imageView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
private func configureSelectionView()
{
selectionView.backgroundColor = .clear
selectionView.layer.borderWidth = 3
selectionView.layer.borderColor = UIColor.clear.cgColor
addSubview(selectionView)
selectionView.translatesAutoresizingMaskIntoConstraints = false
selectionView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
selectionView.topAnchor.constraint(equalTo: topAnchor).isActive = true
selectionView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
selectionView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
configureTapGestureRecognizer()
}
private func configureTapGestureRecognizer()
{
let tapGesture = UITapGestureRecognizer(target: self,
action: #selector(didTapSelectionView))
selectionView.addGestureRecognizer(tapGesture)
}
#objc
private func didTapSelectionView()
{
isSelected = !isSelected
}
private func toggleBorder(_ on: Bool)
{
if on
{
selectionView.layer.borderColor = UIColor(red: 28.0/255.0,
green: 244.0/255.0,
blue: 162.0/255.0,
alpha: 1.0).cgColor
return
}
selectionView.layer.borderColor = UIColor.clear.cgColor
}
}
Then in the view controller
class ClippingTestViewController: UIViewController
{
private let mainContainerView = UIView()
private let customView = CustomBorderView()
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = .white
title = "Clipping view"
configureMainContainerView()
configureCustomBorderView()
mainContainerView.layer.cornerRadius = 50
mainContainerView.clipsToBounds = true
}
private func configureMainContainerView()
{
mainContainerView.backgroundColor = .white
view.addSubview(mainContainerView)
mainContainerView.translatesAutoresizingMaskIntoConstraints = false
mainContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor,
constant: 20).isActive = true
mainContainerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor,
constant: 20).isActive = true
mainContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor,
constant: -20).isActive = true
mainContainerView.heightAnchor.constraint(equalToConstant: 300).isActive = true
view.layoutIfNeeded()
}
private func configureCustomBorderView()
{
mainContainerView.addSubview(customView)
customView.translatesAutoresizingMaskIntoConstraints = false
customView.leadingAnchor.constraint(equalTo: mainContainerView.leadingAnchor).isActive = true
customView.topAnchor.constraint(equalTo: mainContainerView.safeAreaLayoutGuide.topAnchor).isActive = true
customView.trailingAnchor.constraint(equalTo: mainContainerView.trailingAnchor).isActive = true
customView.bottomAnchor.constraint(equalTo: mainContainerView.bottomAnchor).isActive = true
view.layoutIfNeeded()
}
}
This gives me your current experience
Work Around 1. - Shrink subviews on selection
When the view is not selected, everything looks fine. When the view is selected, you could reduce the width and height of the custom subview with some animation while adding the border.
Work Around 2. - Manually clip desired subviews
You go through each subview in your container view and:
Apply the clipping to any subview you desire
Apply the corner radius to the views you clip
Leaving the container view unclipped and without a corner radius
To do that, I created a custom UIView subclass for the container view
class ClippingSubView: UIView
{
override var clipsToBounds: Bool
{
didSet
{
if clipsToBounds
{
clipsToBounds = false
clipImageViews(in: self)
layer.cornerRadius = 0
}
}
}
// Recursively go through all subviews
private func clipImageViews(in view: UIView)
{
for subview in view.subviews
{
// I am only checking image view, you could check which you want
if subview is UIImageView
{
print(layer.cornerRadius)
subview.layer.cornerRadius = layer.cornerRadius
subview.clipsToBounds = true
}
clipImageViews(in: subview)
}
}
}
Then make sure to adjust the following lines where you create your views:
let mainContainerView = ClippingSubView()
// Do this only after you have added all the subviews for this to work
mainContainerView.layer.cornerRadius = 50
mainContainerView.clipsToBounds = true
This gives me your desired output
This is a pretty common problem which may have multiple solutions. In the end though I always find it best to simply go one level higher:
ContainerView (Does not clip)
ContentView (Clips)
HighlightingView (Does not clip)
You would put all your current views on ContentView. Then introduce another view which represents your selection and put it on the same level as your ContentView.
In the end this will give you most flexibility. It can still get a bit more complicated when you add things like shadows. But again "more views" is usually the end solution.
You'll likely run into a lot of problems trying to get a subview's border to display outside its superView's clipping bounds.
One approach is to add an "Outline View" as a sibling of the "Clipping View":
When you select a clippingView's subview - and drag it around - set the frame of the outlineView to match the frame of that subview.
You'll want to set .isUserInteractionEnabled = false on the outlineView so it doesn't interfere with touches on the subviews.
I have a view (VIEW A). I need to get it's position inside of it's superview (ideally a CGPoint for it's Center X and Center Y).
This would be easy, except that when I add VIEW A into the view, I set it's frame to be .zero, and use NSLayoutConstraint anchors to position it. So later, when I want to get it's frame (I've also tried it's bounds), it comes back as (0,0,0,0).
VIEW A is visible and positioned inside it's superview...so it has some X/Y coordinates and width/height right? How do I access these values?
A safe place to get access to updated frames is on viewDidLayoutSubviews after the call to super's implementation
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Here we have access to updated frames
}
If you are adding the subview, say, in viewDidLoad, then the UIViewController hasn't make any calculation for the frames. It just has the rules (constraints) describing the relations between its views. In the lifecycle of a UIViewController, viewDidLayoutSubviews is the point where the frame calculations has already been done and it's safe to access them. You can even calculate some frames yourself after the super call.
Call setNeedsLayout() AND layoutIfNeeded() on the parent view:
let myView = UIView(frame: .zero)
myView.backgroundColor = .red
myView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myView)
myView.topAnchor.constraintEqualToSystemSpacingBelow(view.topAnchor, multiplier: 1).isActive = true
myView.leftAnchor.constraintEqualToSystemSpacingAfter(view.leftAnchor, multiplier: 1).isActive = true
myView.widthAnchor.constraint(equalToConstant: 50).isActive = true
myView.heightAnchor.constraint(equalToConstant: 50).isActive = true
view.setNeedsLayout()
view.layoutIfNeeded()
print(myView.frame)
I am trying to make a photo viewer similar to the Photo's app where the user can zoom in and out on a particular image.
The image is initially sized so fit on the screen but when zoomed can expand to cover the entire screen.
I have this happening in a collectionView cell which is the size of the screen and has paging enabled. In that cell is a scrollView with storyboard constraints set to top/bottom/leading/trailing to it's superview. The rest happens in the code below which is the custom cell.
According to the new behavior of the scrollView introduced in iOS 11, the contentView (imageView in my case) should be centered in the scrollView using:
imageView.centerXAnchor.constraint(equalTo: scrollView.contentLayoutGuide.centerXAnchor).isActive = true
imageView.centerYAnchor.constraint(equalTo: scrollView.contentLayoutGuide.centerYAnchor).isActive = true
However I don't see it explained anywhere and adding those two lines to my code do absolutely nothing.
The imageView inside the scrollView continues to be positioned in the top left corner.
Hopefully somebody has figured out how to do this and can help.
import UIKit
class ImageCollectionViewCell: UICollectionViewCell, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView! {
didSet {
scrollView.delegate = self
scrollView.minimumZoomScale = 0.2
scrollView.maximumZoomScale = 2.0
scrollView.contentSize = imageView.frame.size
scrollView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.widthAnchor.constraint(equalToConstant: 350).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 350).isActive = true
imageView.centerXAnchor.constraint(equalTo: scrollView.contentLayoutGuide.centerXAnchor).isActive = true
imageView.centerYAnchor.constraint(equalTo: scrollView.contentLayoutGuide.centerYAnchor).isActive = true
}
}
var imageView = UIImageView()
var image: UIImage? {
get {
return imageView.image
}
set {
imageView.image = newValue
scrollView?.contentSize = imageView.frame.size
}
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.imageView
}
}
I have been facing the same problem for about a day, and I finally figured it out. It is not sufficient to just add the width and height constraints to the image. For this to work using contentLayoutGuide, the image view actually has to be the same size as the scroll view. Therefore, you will have to do either of the following:
Option 1
Change the width and height constraints of the image view to be the same size as the scroll view.
imageView.widthAnchor.constraint(equalToConstant: scrollView.bounds.width).isActive = true
imageView.heightAnchor.constraint(equalToConstant: scrollView.bounds.height).isActive = true
Option 2 - Preferred
Remove the width and height constraints for the image view. Instead, add constraints pinning the top, bottom, leading, and trailing anchors of the image view to the scroll view. I tried both, and this method seemed to work the best.
imageView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
imageView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
Depending on how/where you are using this, you may need to use scrollView.layoutMarginGuide for the anchor.
I would also recommend moving this code from didSet to a private method named setup(), or something along those lines. Then, from viewWillAppear, call setup(). This is a more consistent way of doing it. Hope this helped!
I have a programmatically made view controller with subviews that are positioned using constraints. When I push this view controller into view using a navigation controller with the animation disabled...
let viewController = InventoryViewController()
navigationController?.pushViewController(viewController, animated: false)
...the view controller does not simply appear on screen, its subviews (the ones with constraints) expand outward and into view. Subviews that are not given constraints simply appear on screen as expected. So obviously, auto layout is animating itself into view. How do I disable this animation and just have the subviews appear on screen?
class InventoryViewController: UIViewController {
override func loadView() {
view = UIView()
view.frame = UIScreen.main.bounds
view.backgroundColor = UIColor.white
addButton()
}
func addButton() {
let button = UIButton()
button.backgroundColor = UIColor.black
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
button.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16).isActive = true
button.topAnchor.constraint(equalTo: view.topAnchor, constant: 36).isActive = true
button.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -32).isActive = true
button.heightAnchor.constraint(equalToConstant: 48).isActive = true
}
#objc func buttonAction() {
//
}
}
Pushing the view controller is what causes its view to be loaded. You could try to force this to load and layout before the push:
let viewController = InventoryViewController()
viewController.view.layoutIfNeeded()
navigationController?.pushViewController(viewController, animated: false)
loadView() is not a good place to configure your subviews, it should be used only to put the views in your view hierarchy, probably that is why you view is animating.
Try to set your subviews on viewDidLoad() method.
Please look the discussion section in the official documentation: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621454-loadview
Call your method from viewDidLayoutSubviews after super.viewDidLayoutSubviews. And animation will not be seen. As the reason behind this is using Autolayout, frame of your views are set when the autolayout engine starts its calculation.
This method is called when the autolayout engine has finished to calculate your views' frames
use
UIView.setAnimationsEnabled(false)
You use layoutIfNeeded() on the view they are added to, to force layout/redraw immediately.
Do this in viewDidLoad or try right after you have set the constraints.
or try:
layoutSubviews()
setNeedsLayout()
layoutSubviews()
I have a UIView in which I have a circular border around it. Here is the code for it:
let v = UIView()
self.view.addSubview(v)
v.backgroundColor = .orange
v.translatesAutoresizingMaskIntoConstraints = false
v.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
v.rightAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
v.topAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
v.heightAnchor.constraint(equalTo: v.widthAnchor).isActive = true
self.view.layoutIfNeeded()
v.layer.cornerRadius = v.frame.width / 2
If I set the ViewController view like this: self.view = mainView (mainView is a subclass of MainView which contains some other subviews), then the result of the corner radius is no longer a circle: Resulting "circle".
However, if I use self.view.addSubview(mainView) (and add autolayout constraints to mainView) and replace self.view.addSubview(v) with self.mainView.addSubview(v) then the circle turns out to be fine.
Why does the circle turn out weird only when I set self.view = mainView, but is fine when I do self.view.addSubview(mainView)?
First where are you replacing the UIViewControllers view with mainView? You should be overriding the loadView method of the UIViewController not doing it in the viewDidLoad method.
Second if you are changing the UIViewControllers view then it will not have it's frame setup when you get to viewDidLoad like the original will and so the UIView v is getting the incorrect layout size which it is then using to determine its corner radius.
Third you shouldn't use layoutIfNeeded in the viewDidLoad as this is way to early to be trying to determine the final layout of the main view (everything is still loading). What you should be doing is overriding viewDidLLayoutSubviews method and setting the corner radius there like this:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
v.layer.cornerRadius = v.frame.width / 2
}
What that also does is if the size of the UIView v changes (orientation change, etc) then it will remain a circle at it's new size otherwise it will just have rounded corners.
Of course you have to make UIView v a class instance variable in order to be able to access it here.