Swift -Position new UIWIndow in center of Screen - ios

The user taps a cell, I get the touchLocation, create and then animate a new UIWindow to the center of the screen. The problem is when the new window (purple) appears on screen instead of its center being in the center of the screen, its x,y coordinate starts at the center of the screen.
I get this:
But I want this:
// the collectionView is dimmed black, that's why the background color is dark
guard let keyWindow = UIApplication.shared.keyWindow else { return }
let startingRect = CGRect(x: userTouchPointX, y: userTouchPointY, width: 10, height: 10)
let newWindow = UIWindow(frame: startingRect)
newWindow.backgroundColor = .clear
newWindow.windowLevel = UIWindow.Level.normal
newWindow.rootViewController = UINavigationController(rootViewController: PurpleVC())
newWindow.isHidden = false
newWindow.makeKey()
let endingCenterX = keyWindow.screen.bounds.midX // I also tried keyWindow.frame.width / 2 and also UIScreen.main.bounds.width / 2
let endingCenterY = keyWindow.screen.bounds.midY // I also tried keyWindow.frame.height / 2 and also UIScreen.main.bounds.height / 2
let endingWidth = keyWindow.frame.width
let endingHeight: CGFloat = 200
let endingCenter = CGPoint(x: endingCenterX, y: endingCenterY)
UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [.curveEaseIn], animations: {
newWindow.center = endingCenter
newWindow.frame.size.width = endingWidth
newWindow.frame.size.height = endingHeight
}) { (_) in
newWindow.layoutIfNeeded()
}

Change your center After setting the frame of the window will solve your issue ... hopefully it will help
// newWindow.center = endingCenter remove from here
newWindow.frame.size.width = endingWidth
newWindow.frame.size.height = endingHeight
newWindow.center = endingCenter // add it here

Related

UIButton loses responsivity(i.e can't press it), maybe because of CGAffineTransform

Firstly, I think that the problem is on the UIVisualEffectView, but now I think that the problem is on the CGAffineTransform that I'm using to animate the view on the openOptionsTooltip()
First, I found a similar problem on that question (see the comments on the accepted answer), but in my case I'm adding a button as subview of the UIVisualEffectView, not adding it as a button subview.
Apple documentation says:
Depending on the desired effect, the effect may affect content layered behind the view or content added to the visual effect view’s contentView.
But I can't find a better explanation on how or why it can affect the content added to the contentView.
I'm doing all the stuff programmatically, without any xib.
The visual effect view is a balloon like a tooltip that appears when I touch the titleView.
I'm creating that view on viewDidLoad and holding a
reference to it on the viewController.
After created, when I click on the titleView I'm adding it as a subview of UIWindow
Here's my code:
The method to create the view:
func createOptionsTooltip(withNamesAndActions options: [(name: String , action: Selector)]){
self.tooltipSize = CGSize(width: 250, height: CGFloat(60*options.count+12))
var optionItens : [UIView] = []
for i in 0..<options.count{
let optionView = UIView(frame: CGRect(x: .zero, y: CGFloat(60*i), width: self.tooltipSize.width, height: 60))
optionView.backgroundColor = .clear
let separatorView = UIView(frame: CGRect(x: .zero, y: 59, width: self.tooltipSize.width, height: 1))
separatorView.backgroundColor = UIColor(white: 0.8, alpha: 0.5)
let button = UIButton(frame: CGRect(x: .zero, y: .zero, width: self.tooltipSize.width, height: 59))
button.setTitle(options[i].name, for: .normal)
button.setTitleColor(self.view.tintColor, for: .normal)
button.addTarget(self, action: options[i].action, for: .touchUpInside)
button.isEnabled = true
button.isUserInteractionEnabled = true
optionView.addSubview(button)
optionView.addSubview(separatorView)
optionView.bringSubviewToFront(button)
optionItens.append(optionView)
}
self.blackOpaqueView = UIView(frame: self.view.bounds)
self.blackOpaqueView.backgroundColor = UIColor.black
self.blackOpaqueView.alpha = 0
let gesture = UITapGestureRecognizer(target: self, action: #selector(ScheduleNavigationController.closeOptionsTooltip))
// blackOpaqueView.addGestureRecognizer(gesture)
self.vwTooltip = UIView(frame: CGRect(origin: CGPoint(x: self.view.frame.midX - self.tooltipSize.width/2, y: self.view.frame.minY+self.navigationBar.frame.size.height+UIApplication.shared.statusBarFrame.height+10), size: CGSize(width: 1, height: 1)))
self.vwTooltip.transform = self.vwTooltip.transform.scaledBy(x: 1, y: 0.01)
self.vwTooltip.isHidden = true
let vwAim = UIVisualEffectView(frame: CGRect(x: self.tooltipSize.width/2 - 6, y: 0, width: 12, height: 10))
let vwBalloon = UIVisualEffectView(frame: CGRect(x: 0, y: vwAim.frame.size.height, width: self.tooltipSize.width, height: CGFloat(60*options.count)))
vwBalloon.cornerRadius = 8
vwBalloon.contentView.isUserInteractionEnabled = true
vwBalloon.isUserInteractionEnabled = true
let bezPath = trianglePathWithCenter(rect: vwAim.frame)
let maskForPath = CAShapeLayer()
maskForPath.path = bezPath.cgPath
vwAim.layer.mask = maskForPath
vwBalloon.effect = UIBlurEffect(style: .prominent)
vwAim.effect = UIBlurEffect(style: .prominent)
for optionView in optionItens{
vwBalloon.contentView.addSubview(optionView)
vwBalloon.bringSubviewToFront(optionView)
}
self.vwTooltip.addSubview(vwBalloon)
self.vwTooltip.addSubview(vwAim)
self.vwTooltip.bringSubviewToFront(vwBalloon)
vwBalloon.bringSubviewToFront(vwBalloon.contentView)
}
And I'm calling't on the viewDidLoad that way:
createOptionsTooltip(withNamesAndActions: [("Sair", #selector(ScheduleNavigationController.closeSchedule(_:))), ("Hoje", #selector(ScheduleNavigationController.scrollToToday(_:)))])
And I'm open the tooltip animated this way:
func openOptionsTooltip(){
if let appDelegate = UIApplication.shared.delegate as? AppDelegate{
appDelegate.window?.addSubview(blackOpaqueView)
appDelegate.window?.addSubview(vwTooltip)
appDelegate.window?.bringSubviewToFront(vwTooltip)
UIView.animate(withDuration: 0.2, animations: {
self.blackOpaqueView.alpha = 0.5
}) { _ in
self.vwTooltip.isHidden = false
UIView.animate(withDuration: 0.1, animations: {
self.vwTooltip.transform = self.vwTooltip.transform.scaledBy(x: 1, y: 100)
})
}
}
}
What I have tried:
Bring the buttons and all it's superview in hierarch to front using bringSubviewToFront. (I have checked, there's no view on the front of the button)
change the isUserInteractEnabled for all button superviews in hierarch.(for both false and true)
Change the way that I'm adding the target to buttons, without using the method parameter.
Change the selector method to any other method that work on other targets and switch the method between #objc and #IBOulet
Change the buttons to a view other than the UIVisualEffectView
PS: I found that the UIVisualEffectView has 3 subviews, but apparently the contentView is the above view, see the log of printing it's subviews:
▿ 3 elements
- 0 : <_UIVisualEffectBackdropView: 0x102dcd140; frame = (0 0; 250 120); autoresize = W+H; userInteractionEnabled = NO; layer = <UICABackdropLayer: 0x280d1b480>>
- 1 : <_UIVisualEffectSubview: 0x102dcd350; frame = (0 0; 250 120); autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x280d15c80>>
- 2 : <_UIVisualEffectContentView: 0x102dcc940; frame = (0 0; 250 120); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x280d1ace0>>
I can't figure out what is blocking the button's user interaction
Here's the result:
I have found the problem:
After calling the openOptionsTooltip() the transform inside't are resizing all it's subview, but the vwTooltp itself don't change it's frames, but because it clipToBounds property are false, all the subviews are showing, but the user can't access the button actions because the view has 1x1 size. Changing the openOptionsTooltip() method to that:
func openOptionsTooltip(){
if let appDelegate = UIApplication.shared.delegate as? AppDelegate{
appDelegate.window?.addSubview(blackOpaqueView)
appDelegate.window?.addSubview(vwTooltip)
appDelegate.window?.bringSubviewToFront(vwTooltip)
UIView.animate(withDuration: 0.2, delay: 0.0, options: UIView.AnimationOptions.allowUserInteraction, animations:{
self.blackOpaqueView.alpha = 0.5
}) { _ in
self.vwTooltip.isHidden = false
UIView.animate(withDuration: 0.1, delay: 0.0, options: UIView.AnimationOptions.allowUserInteraction, animations:{
self.vwTooltip.transform = self.vwTooltip.transform.translatedBy(x: 1, y: 1)
self.vwTooltip.transform = self.vwTooltip.transform.scaledBy(x: 1, y: 100)
}){ _ in
self.vwTooltip.frame = self.tooltipFrame
}
}
}
}
And now it's working properly. Thanks for the responses

UIView.animate problems with view frame

I'm struggling trying to make an animation. This is what's happening: video
The back card was supposed to do the same animation as in the beginning of the video, but it's doing a completely different thing. I'm checking the UIView.frame at the beginning of the animation and it's the same as the first time the card enters, but obviously something is wrong... Here is the code:
func cardIn() {
let xPosition = (self.darkCardView?.frame.origin.x)! - 300
let yPosition = (self.darkCardView?.frame.origin.y)!
self.initialPos = self.darkCardView.frame
let height = self.darkCardView?.frame.size.height
let width = self.darkCardView?.frame.size.width
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(Double.pi/4)))!
UIView.animate(withDuration: 1.1, animations: {
self.darkCardView?.frame = CGRect(x: xPosition, y: yPosition, width: width!, height: height!)
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(0)))!
})
}
func cardOut() {
let xPosition = (self.darkCardView?.frame.origin.x)! - 600
let yPosition = (self.darkCardView?.frame.origin.y)!
let height = self.darkCardView?.frame.size.height
let width = self.darkCardView?.frame.size.width
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(0)))!
UIView.animate(withDuration: 1.0, delay: 0, options: .allowAnimatedContent, animations: {
self.darkCardView?.frame = CGRect(x: xPosition, y: yPosition, width: width!, height: height!)
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(-Double.pi/4)))!
}) { (true) in
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(0)))!
self.darkCardView?.frame = self.initialPos
self.cardIn()
}
}
Does somebody know how can I repeat the same animation that's in the beginning of the video after cardOut function is called?
Given that we do not know where/when you are calling the above two functions I can only take a guess at what is happening.
So you have an animate in and an animate out, but what about a reset function?
Timeline:
Animate the card in.
...
Eventually animate the card out
...
Reset the cards location to off screen to the right (location before animating in for the first time)
...
animate the card in
...
Repeat, ect....
I managed to solve the animation problem by calling viewDidLoad() instead of calling self.cardIn() in the end o cardOut completion block. But I don't want to depend on calling viewDidLoad every time a new card has to enter the screen...
Also, I didn't set any properties of the back card in viewDidLoad. I only have it in the .xib file. Any ideas?
Note that if you set a view's transform to anything other than the identity transform then the view's frame rect becomes "undefined". You can neither set it to a new value nor read it's value reliably.
You should change your code to use the view's center property if you're changing the transform.
As Jerland points out in their post, you also probably need to reset your back card to it's starting position before beginning the next animation cycle. (Set it to hidden, put it's center back to the starting position and set it's transform back to the identity transform.
EDIT:
This code:
UIView.animate(withDuration: 1.0, delay: 0, options: .allowAnimatedContent, animations: {
self.darkCardView?.frame = CGRect(x: xPosition, y: yPosition, width: width!, height: height!)
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(-Double.pi/4)))!
}) { (true) in
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(0)))!
self.darkCardView?.frame = self.initialPos
self.cardIn()
}
Does not set the transform to identity. Try changing that line to
self.darkCardView?.transform = .identity

Issue getting the correct image height based on different image types

On my application I've been working on tapping an image and enlarging the image and display the image full size.
Ideally this is what I want to get working:
(Didn't let me add video here so here's the link to it)
https://media.giphy.com/media/F7wCO7miVMG6k/source.mp4
However, I'm only able to display images on the correct size if their height is not that tall.
If I have a square image, or an image too large, the image appears as it follows:
Here's the code that handles how the image is displayed:
var startingFrame: CGRect?
var blackBackgroundView: UIView?
var startingImageView: UIImageView?
func performZoomInForStartingImageView(startingImageView: UIImageView){
print("starting image view: ", startingImageView.bounds)
//startingImageView.sizeToFit()
self.startingImageView = startingImageView
self.startingImageView?.isHidden = true
startingFrame = startingImageView.superview?.convert(startingImageView.frame, to: nil)
print("starting frame: ", startingFrame!)
let zoomingImageView = UIImageView(frame: startingFrame!)
//zoomingImageView.backgroundColor = UIColor.red
zoomingImageView.image = startingImageView.image
print("zoom image view: ", zoomingImageView.bounds )
zoomingImageView.isUserInteractionEnabled = true
//zoomingImageView.autoresizingMask = [.flexibleWidth, .flexibleHeight, .flexibleBottomMargin, .flexibleRightMargin, .flexibleLeftMargin, .flexibleTopMargin]
//zoomingImageView.contentMode = UIViewContentMode.scaleAspectFit
zoomingImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleZoomOut)))
if let keyWindow = UIApplication.shared.keyWindow {
print("key window frame: ", keyWindow.frame)
blackBackgroundView = UIView(frame: keyWindow.frame)
blackBackgroundView?.backgroundColor = UIColor.black
blackBackgroundView?.alpha = 0
keyWindow.addSubview(blackBackgroundView!)
keyWindow.addSubview(zoomingImageView)
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackBackgroundView?.alpha = 1
print("keywindow frame width: ", keyWindow.frame.width)
print("keywindow frame height: ", keyWindow.frame.height)
print("\nstarting frame height: ", self.startingFrame!.height)
print("starting frame width: ", self.startingFrame!.width)
let height = (self.startingFrame!.height / self.startingFrame!.width) * keyWindow.frame.width
print("height in animate:", height)
//let height = self.startingFrame!.height * 2
zoomingImageView.frame = CGRect(x: 0, y: 0, width: keyWindow.frame.width, height: height)
print("zooming image view frame: ", zoomingImageView.frame)
zoomingImageView.center = keyWindow.center
}, completion: nil)
}
}
Note: I added a bunch of print statements to see exactly what was going on with the frame
I also noticed that my issue happens in the following line:
let height = (self.startingFrame!.height / self.startingFrame!.width) * keyWindow.frame.width
By using keyWindow.frame.width it displays the image with short height correctly, but the other photos height is not correct.
If I change the line to:
let height = (self.startingFrame!.height / self.startingFrame!.width) * keyWindow.frame.height
it displays the square images correctly, but not the ones with short height.
How can I detect the correct height of the image and display them correctly based on their height?
Edit:
Square image log
Wide image log
Edit 2: New Logs
Square image log
Wide image log
you can try like adding an if condition to check the image is landscape or portrait and set height accordingly,
let image = startingImageView.image
let w = image.size.width
let h = image.size.height
print("image width: ", w)
print("image height: ", h)
var height = 0
if (w > h) { //landscape
height = (self.startingFrame!.height / self.startingFrame!.width) * keyWindow.frame.width
} else { //portrait
height = (self.startingFrame!.height / self.startingFrame!.width) * keyWindow.frame.height
}

Change width of UICollection view dynamically when x coordinates changes on TouchMoved function

everybody.
I am making a slide out menu in swift. I have made everything working fine. The slide out menu shows when hamburger button is clicked and also when right swipe on the screen is done. Now I want to make the size (width) of a slide out menu change based on the user coordinates on the screen.
The slide out is implemented using collection view on top of a UIView.
To take the user's position from the screen I have used touchMoved function to get user's real-time x value. Is there any method to use this value so that collection's view's width can be changed.
Any help will be very helpful. If needed I can upload all the codes. Changing the anchor size also didn't help.
MoreOptionsViewController.swift
override init() {
super.init();
collectionView.delegate = self;
collectionView.dataSource = self;
collectionView.register(MoreOptionsCell.self, forCellWithReuseIdentifier: cellId);
getData();
}
func showMoreOptions(){
if let window = UIApplication.shared.keyWindow{
window.addSubview(view);
view.setAnchor(top: window.topAnchor, bottom: window.bottomAnchor, left: window.leftAnchor, right: window.rightAnchor);
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTapOutside)));
view.alpha = 0.5;
collectionView.alpha = 1;
window.addSubview(collectionView);
let width = window.frame.width * 0.9;
//Animate the collection view's width
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.collectionView.leftAnchor.constraint(equalTo: window.leftAnchor).isActive = true;
self.collectionView.topAnchor.constraint(equalTo: window.topAnchor).isActive = true;
self.collectionView.widthAnchor.constraint(equalToConstant: width).isActive = true;
self.collectionView.heightAnchor.constraint(equalTo: window.heightAnchor).isActive = true;
}, completion: nil)
}
}
func handleTapOutside(){
collectionView.removeFromSuperview();
view.removeFromSuperview();
}
let view: UIView = {
let view = UIView();
view.backgroundColor = UIColor.black;
view.translatesAutoresizingMaskIntoConstraints = false;
return view;
}();
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout();
layout.minimumLineSpacing = 0;
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout);
cv.showsVerticalScrollIndicator = false;
cv.backgroundColor = UIColor(red: 59/255, green: 65/255, blue: 65/255, alpha: 0.9);
cv.translatesAutoresizingMaskIntoConstraints = false;
return cv;
}();
The above code contains the actual collection view to present the slide out menu.
If i try to change the width from touchMoved function I get a lot of error. Any help will be much appreciated.
Try setting the collection view's width by updating its frame in your touchMoved function
yourCollectionView.frame = CGRect(x: 0, y: 0, width: <user's_x_scroll_value>, height: height)
Solved the problem. The problem was caused due to the animation code.
func showMoreOptions(xValue: CGFloat){
if let window = UIApplication.shared.keyWindow{
window.addSubview(view);
//Display the view beyond the screen. Increase the x coordinates according to the x value of screen
view.frame = CGRect(x: -window.frame.width + xValue , y: 0, width: window.frame.width, height: window.frame.height);
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTapOutside)));
print("View's x value is ", xValue)
view.alpha = 0.011; //Use minimum alpha value
collectionView.alpha = 1;
window.addSubview(collectionView);
//Same as above view
collectionView.frame = CGRect(x: -window.frame.width + xValue, y: 0, width: window.frame.width * 0.9, height: window.frame.height);
view.layoutIfNeeded()
collectionView.collectionViewLayout.invalidateLayout();
}
}
Also collectionView.collectionViewLayout.invalidateLayout() fixed the problem. Thank you Eli whittle for the help

View controller origin changes every time it's presented

This is what my view controller should be:
This is what it is sometimes:
I want to display a view controller in the circle, however, almost every time the view controller in the circle (ResultViewController) is presented, it's place is different, though its properties doesn't change at all. Here's my code:
func openCircle(withCenter center: CGPoint, dataSource: ([Items], Int, String)){
self.addCircle(withCenter: center, dataSource: dataSource)
}
func addCircle(withCenter circleCenter: CGPoint, dataSource: ([Items], Int, String)) {
let longerSide = fmax(view.frame.size.height, view.frame.size.width)
let shorterSide = fmin(view.frame.size.height, view.frame.size.width)
let circleRadius = longerSide / 2
var resultViewOrigin = CGPoint()
var resultViewSize = CGSize()
if UIDevice.current.userInterfaceIdiom == .pad {
let rectWidth = shorterSide / 2
let rectHeight = sqrt(abs(circleRadius * circleRadius - rectWidth * rectWidth)) + view.frame.size.height - circleCenter.y
resultViewSize = CGSize(width: rectWidth, height: rectHeight)
resultViewOrigin = CGPoint(x: (view.frame.size.width - rectWidth) / 2, y: view.frame.size.height - rectHeight)
} else {
resultViewOrigin = CGPoint(x: 0.0, y: 0.0)
resultViewSize = CGSize(width: view.frame.size.width, height: view.frame.size.height)
}
let resultViewController = UIStoryboard(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "ResultVC") as! ResultViewController
resultViewController.transitioningDelegate = self
resultViewController.modalPresentationStyle = .custom
resultViewController.dataSource = dataSource
resultViewController.view.frame = CGRect(origin: resultViewOrigin, size: resultViewSize)
transition.circle = UIView()
transition.startingPoint = circleCenter
transition.radius = circleRadius
transition.circle.frame = circleFrame(radius: transition.radius, center: transition.startingPoint)
present(resultViewController, animated: true)
}
It works well on the iPhone, not on the iPad, what's the problem?
I found the problem, it's actually a missing constraint on Regular-Regular size class caused this problem, I fixed it by adding a spacing to bottom layout guide to the part that used to get misplaced.
Thanks to everybody for your idea.
You can use a container view instead of presenting the view controller. You can create them programmatically or in interface builder (see Apple docs).

Resources