show UIView like its coming from another view - swift

I am creating a popout view for some selection purpose now what I have done is hide the whole UI to the right side of the mobile view and when the user taps on the button slide UI in the visile area for which I have written
For open :
let originalTransform = self.rightPopView.transform
let scaledAndTranslatedTransform = originalTransform.translatedBy(x: -330, y: 0)
UIView.animate(withDuration: 0.5, animations: {
self.rightPopView.transform = scaledAndTranslatedTransform
})
For close :
let originalTransform = self.rightPopView.transform
let scaledAndTranslatedTransform = originalTransform.translatedBy(x: 330, y: 0)
UIView.animate(withDuration: 0.5, animations: {
self.rightPopView.transform = scaledAndTranslatedTransform
})
with this, I have achieved this result
as you can see in this image view I am trying to show hide is visible on the back of the vertical bar view
what i want is uiview should open from a vertical bar, not screen.

Related

iOS Swift - Position subview based on button frame (button is inside Vertical Stack View)

Update
After adding convert method between rect and main View, the Y position is ok, but X coordinate is shifted to the right outside of the main view:
Dropdown view(subview) is off main view
Below is button frame before and after convert method. Main view is 414 x 896. Dropdown menu somehow shifts to the right as on attached image.
button frame in stackView btnRect: (120.66666666666666, 0.0, 293.3333333333333, 30.0)
button frame in main view: cvtRect (241.33333333333331, 190.0, 293.3333333333333, 30.0)
view: Optional(>)
Goal. I want to make a dropdown list by showing UIView with dropdown options below a button. Button is inside of Vertical Stack View. I add a TableView with dropdown options to this dropdown UIView.I want to click a button and have this UIView with TableView inside to show just below the button. Basically following this tutorial https://www.youtube.com/watch?v=D3DCPaEE4hQ with the exception that my button is inside of Vertical Stack View.
Issue. UIView and TableView inside UIView show up ok when button is clicked. The issue is dropdown UIView's location that is always the same origin X=120, Y=0.
This is how I try to do it:
I have Vertical Stack with 4 rows
In 4th row I have label(width=120 almost same as X coordinate above) and a button that triggers UIView to show
I am using button to show dropdown list(basically UIView) that should appear just below button when the button is tapped, but it always appears at origin x=120 Y=0 , basically pinned to top of the right column in Vertical Stack View. Vertical Stack View has 1st column with labels, and second column with different controls like buttons etc.
func addTransparentView(frames: CGRect)
{
let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
transparentView.frame = window?.frame ?? self.view.frame
//some of the stuff I tried to at least centre dropdownUIView
//transparentView.center.x = window?.center.x ?? self.view.center.x
//transparentView.center.y = window?.center.y ?? self.view.center.y
self.view.addSubview(transparentView)
tvPriority.frame = CGRect(x: frames.origin.x, y: frames.origin.y + frames.width, width: frames.width, height: 0)
//some of the stuff I tried to at least centre UIView
//tvPriority.center = verticalStackView.convert(verticalStackView.center, from:tvPriority)
//tvPriority.center.x = view.center.x
//tvPriority.center.y = view.center.y
self.view.addSubview(tvPriority)
tvPriority.layer.cornerRadius = 5
transparentView.backgroundColor = UIColor.black.withAlphaComponent(0.9)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(removeTransparentView))
transparentView.addGestureRecognizer(tapGesture)
transparentView.alpha = 0
UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: UIView.AnimationOptions.curveEaseInOut, animations: {self.transparentView.alpha = 0.5
self.tvPriority.frame = CGRect(x: frames.origin.x, y: frames.origin.y + frames.height, width: frames.width, height: 193)
}, completion: nil)
}
To successfully make dropdown list I need UIView to show up just below buttons frame(X, Y, width, height). But although button is in the 4th row which should be position with much higher Y value, buttons frame is always at X=120, Y=0, so my UIView is always pinned to this location way above button that is supposed to simulate dropdown.
Questions
1. What am I missing with positioning of the dropdown UIView? Why is buttons position Y=0 when the button is in 4th row of Vertical Stack View, with obviously much higher Y position? I also tried to simply centre this dropdown in the centre of screen but that also does not work.
2. I transitioned to iOS development from the world of web development, and I used dropdown a lot in my career. Should I just use Picker View instead? Or alert? What is the most common and most standard way of offering list of mutually exclusive options to user in Swift app?
Thanks a lot
Your button is a subview of the stackView, so its frame is relative to the frame of the stackView.
To get its frame (rect) in the view's coordinate space, you'll want to use .convert. Assign this action to one of your buttons in the stackView:
EDIT Fixed the code example... I had not checked it before posting.
class ConvertViewController: UIViewController {
#IBOutlet var dropDownView: UIView!
#IBAction func didTap(_ sender: Any) {
guard let btn = sender as? UIButton else {
fatalError("Sender is not a button!")
}
guard let sv = btn.superview as? UIStackView else {
fatalError("Sender is not in a stackView!")
}
let btnRect = btn.frame
let cvtRect = sv.convert(btn.frame, to: view)
print("button frame in stackView:", btnRect)
print("button frame in main view:", cvtRect)
let dropDownRect = dropDownView.bounds
let cvtCenterX = cvtRect.origin.x + (cvtRect.size.width / 2.0)
let viewX = cvtCenterX - (dropDownRect.width / 2.0)
let newOrigin = CGPoint(x: viewX, y: cvtRect.minY + cvtRect.height)
dropDownView.frame.origin = newOrigin
}
}
If you look at the output in the debug console, you should see something like this:
button frame in stackView: (0.0, 114.0, 46.0, 30.0)
button frame in main view: (164.5, 489.5, 46.0, 30.0)
As you can see, the rect (frame) of my 4th button in my stackView has an origin of 0.0, 114.0, but after converting it to my view coordinate space, the rect's origin is 164.5, 489.5.
You can now position your "dropdown list" relative to the converted rect.
As a side note, you may want to look at UIPopoverPresentationController.

UIView.animateWithDuration not animating after initial animation

I have two methods:
private func showPicker() {
UIView.animate(withDuration: 0.3,
animations: {
var pickerFrame = self.vPickerWrapper.frame
pickerFrame.origin.y = self.view.frame.size.height - 300.0
self.vPickerWrapper.frame = pickerFrame
self.bIsShowingPicker = true
self.view.layoutIfNeeded()
self.view.updateConstraintsIfNeeded()
})
}
private func closeThePicker() {
UIView.animate(withDuration: 0.3,
animations: {
var pickerFrame = self.vPickerWrapper.frame
pickerFrame.origin.y = self.view.frame.size.height + 1
self.vPickerWrapper.frame = pickerFrame
self.bIsShowingPicker = false
self.view.layoutIfNeeded()
self.view.updateConstraintsIfNeeded()
})
....
the first method is called when the user touches a text field that requires them to populate from the pickerview. The second method is called when either the close or select button is touched within the wrapper.
The wrapper is created in storyboard and has constraints (trailing, leading bottom) to position it off screen. I initially made the bottom constraint a IBOutlet and wanted to change its constant to handle the animation. (Which I do with other objects in this VC). I then tried to work with CGAffineTransform.translate and .identity and finally dropped down to working with the frame. In all three cases the wrapper will display but not close (move back off screen).
Here's the value of vPickerWrapper after I set its frame to pickerFrame:
pickerFrame CGRect (origin = (x = 0, y = 668), size = (width = 375, height = 300))
which is what I am expecting, just not any animation in the sim. The view never moves. Any idea why the display is not showing it in the new coordinates?

How to open a NSPopover at a distance from the system bar?

I'm opening a NSPopover with the action of an icon in the status bar.
myPopover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
This works fine with the exception that the distance from the popover and the system bar is zero:
I'd like to achieve the same result as the Dropbox app which renders the popover at a small distance from the system bar:
I've tried using button.bounds.offsetBy(dx: 0.0, dy: 20.0) which doesn't affect the position of the popover and button.bounds.offsetBy(dx: 0.0, dy: -20.0) which puts the popover above the system bar:
So how can I position the NSPopover at some distance from the system bar?
First, the reason why button.bounds.offsetBy(dx: 0.0, dy: -20.0) didn't work is because those coordinate fell outside the "window" of the status bar item which is the status bar itself. So anything outside of it was cropped.
I solved this problem by collecting information here and there:
Create an invisible window.
Find the coordinates in the screen of the status bar item and position the invisible window under it.
Show the NSPopover in relation to the invisible window and not the status bar item.
The red thing is the invisible window (for demonstration purposes).
Swift 4 (Xcode 9.2)
// Create a window
let invisibleWindow = NSWindow(contentRect: NSMakeRect(0, 0, 20, 5), styleMask: .borderless, backing: .buffered, defer: false)
invisibleWindow.backgroundColor = .red
invisibleWindow.alphaValue = 0
if let button = statusBarItem.button {
// find the coordinates of the statusBarItem in screen space
let buttonRect:NSRect = button.convert(button.bounds, to: nil)
let screenRect:NSRect = button.window!.convertToScreen(buttonRect)
// calculate the bottom center position (10 is the half of the window width)
let posX = screenRect.origin.x + (screenRect.width / 2) - 10
let posY = screenRect.origin.y
// position and show the window
invisibleWindow.setFrameOrigin(NSPoint(x: posX, y: posY))
invisibleWindow.makeKeyAndOrderFront(self)
// position and show the NSPopover
mainPopover.show(relativeTo: invisibleWindow.contentView!.frame, of: invisibleWindow.contentView!, preferredEdge: NSRectEdge.minY)
NSApp.activate(ignoringOtherApps: true)
}
I was trying to use show(relativeTo: invisibleWindow.frame ...) and the popup wasn't showing up because NSWindow is not an NSView. For the popup to be displayed a view has to be passed.
you can move the contentView of the popover right after showing it:
myPopover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
let popoverWindowX = myPopover.contentViewController?.view.window?.frame.origin.x ?? 0
let popoverWindowY = myPopover.contentViewController?.view.window?.frame.origin.y ?? 0
myPopover.contentViewController?.view.window?.setFrameOrigin(
NSPoint(x: popoverWindowX, y: popoverWindowY + 20)
)
myPopover.contentViewController?.view.window?.makeKey()
in terms of UI you will get a slight slide of the arrow but in your case, with the very small padding, it'll be most imperceptible.
i'm using something similar to make sure my popover doesn't go offscreen. you can see the slight slide.

Swift ios 10 NavBar Item keeps growing

I have a navBar Controller connected to a UIView, I have a right bar button item that is a chevron. I have programmatically created a search bar. If you click the chevron about 20-100 times it keeps growing until it is off the screen. I can see each time I click the chevron a slight bump in my search bar. Since you can not place constraints on navBar and fixed Space bar button item does not work either. Any suggestions on where to look or how to fix this?
func rotateChevron(animated: Bool = true) {
let chevAnimate = animated ? 0.3 : 0.0
let chevIsDown = messagePicker.frame.origin.y == self.view.frame.height - (tabBarController?.tabBar.frame.height ?? 0)
let rotation: CGFloat = chevIsDown ? 0.0 : 180.001
UIView.animate(withDuration: chevAnimate, animations: {
self.filterChevron.customView?.transform = CGAffineTransform(rotationAngle: rotation.degreesToRadians)
}, completion: nil)
}
After some research I placed within the animation blocks a forced CGSize (w/h) and this seems to have solved the issue. I also found that transform animation should not be used with frames because it moved the bounds of the image each time which is why the chevron was acting up.
func rotateChevron(animated: Bool = true) {
let chevAnimate = animated ? 0.3 : 0.0
let chevIsDown = messagePicker.frame.origin.y == self.view.frame.height - (tabBarController?.tabBar.frame.height ?? 0)
let rotation: CGFloat = chevIsDown ? 0.0 : 180.001
filterChevron.customView?.frame.size = CGSize(width: 35, height: 30)
UIView.animate(withDuration: chevAnimate, animations: {
self.filterChevron.customView?.transform = CGAffineTransform(rotationAngle: rotation.degreesToRadians)
}, completion: nil)
}

Weird UIVisualEffectView behavior in multitasking

I have an AccountsViewController which has UIVisualEffectView with blur effect in background (subview at index 0), so that it covers previous controller with blur during transition. But when I then switch to another app and open multitasking menu again to switch back to my app, its blurry UIVisualEffectView seem to be damaged (as shown on the screenshot 1). When I make my app active again, the view gets "repaired" and looks ok again (as shown on the second screenshot)
Some code from my custom transitioning class where I add the blur view
// in the animateTransition.. method
guard let container = transitionContext.containerView() else {
return
}
let blurEffectView: UIVisualEffectView
if container.viewWithTag(101) != nil {
blurEffectView = container.viewWithTag(101)! as! UIVisualEffectView
}
else {
blurEffectView = UIVisualEffectView(frame: container.frame)
blurEffectView.translatesAutoresizingMaskIntoConstraints = false
blurEffectView.tag = 101
container.addSubview(blurEffectView)
}
if self.isPresenting {
// We're presenting a view controller over another
toViewController.view.transform = CGAffineTransformMakeScale(0.7, 0.7)
container.addSubview(toViewController.view)
container.sendSubviewToBack(blurEffectView)
toViewController.view.alpha = 0
UIView.animateWithDuration(self.transitionDuration(transitionContext), delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: [], animations: { () -> Void in
fromViewController.view.transform = CGAffineTransformMakeScale(0.8, 0.8)
toViewController.view.transform = CGAffineTransformMakeScale(1, 1)
blurEffectView.effect = UIBlurEffect(style: .Dark)
toViewController.view.alpha = 1
}, completion: { (completed) -> Void in
self.context?.completeTransition(completed)
})
}
else {
// We're dismissing a view controller
UIView.animateWithDuration(self.transitionDuration(transitionContext), delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: [], animations: { () -> Void in
fromViewController.view.transform = CGAffineTransformMakeScale(0.7, 0.7)
toViewController.view.transform = CGAffineTransformMakeScale(1, 1)
blurEffectView.effect = nil
fromViewController.view.alpha = 0
}, completion: { (completed) -> Void in
fromViewController.view.removeFromSuperview()
self.context?.completeTransition(completed)
})
}
Seems like I've figured it out myself: this issue appears only when there is an UIWindow back (which is black) showing through.
In this question I had a transition animation container with these views:
fromViewController's view (with a scale transform 0.8;0.8)
UIVisualEffectView with .Dark style blur applied to it
toViewController's view
Because of fromViewController's view size (which was smaller than the screen because of scale transform), the black UIWindow view was showing through. And according to my research this causes UIVisualEffectView to produce a glitch described in this question.
The solution is to add a black UIView with the frame of an UIWindow and add it to the bottom of the container's view hierarchy:
let blackView = UIView(frame: container.frame)
blackView.backgroundColor = UIColor.blackColor()
container.insertSubview(blackView, atIndex: 0)
And then this glitch seem to appear no longer.