I want a popover without rounded corners and with no arrow.
I have done the following code but it did not work:
//SerachPopViewController.swift
//MARK: InitCoder
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//popover settings
//popoverPresentationController!.permittedArrowDirections = .Any
modalPresentationStyle = .Popover
popoverPresentationController!.delegate = self
//permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
self.preferredContentSize = CGSize(width:340,height:380)
}
//QueryTableViewController.swift
#IBAction func searchFilter(sender: AnyObject) {
let searchPopController = storyboard!.instantiateViewControllerWithIdentifier("SerachPopViewController") as! SerachPopViewController
searchPopController.serachPopDelegate = self
searchPopController.modalPresentationStyle = .Popover
searchPopController.preferredContentSize = CGSize(width:340,height:380)
let popoverPresentationController = searchPopController.popoverPresentationController
popoverPresentationController!.sourceView = self.view;
popoverPresentationController!.sourceRect = CGRectMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds),0,0)
popoverPresentationController!.permittedArrowDirections = UIPopoverArrowDirection();
self.presentViewController(searchPopController, animated: true, completion: nil)
}
I am able to display popover view with arrow and rounded arrow.
Please help me to achieve:
popup view with rectangle corner
popup view without direction arrows
Using the concept above, you can also set the corner radius in the completion parameter.
Swift 3
let popoverViewController: UIViewController = // Some view controller to be presented in a popover
// Set popover properties here...
// i.e. popoverViewController.modalPresentationStyle = .popover
present(popoverViewController, animated: true, completion: {
popoverViewController.view.superview?.layer.cornerRadius = 0
// Additional code here
})
In iOS 11 its not possible to use #SHN solution for removing rounded corners. The corner radius is set to default value after viewWillAppear.
Radius must be set in viewDidAppear method
override func viewDidAppear(_ animated: Bool) {
view.superview?.layer.cornerRadius = 0
super.viewDidAppear(animated)
}
To get popover without arrow when you are initiating popover, use:
popover!.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
For popover without corner radius, in the popover content view controller use:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Do any additional setup after loading the view, typically from a nib.
self.view.superview?.layer.cornerRadius = 0.0;
}
I was not 100% happy with shawnynicole's answer because I realized the change from the rounded to the rectangular corners are notable/visible.
So I came up with this: subclass the view controller (in my case it was an UINavigationController) and override viewDidLayoutSubviews and update corners there. This is better because corners change animation is not visible and it will be updated every time needed (on rotations, etc).
It works in iOS11 and probably should work on others versions too.
class PopoverNavigationController: UINavigationController {
#IBInspectable var cornerRadius: Int = -1
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let r = CGFloat(cornerRadius)
if r >= 0 && view.superview?.layer.cornerRadius != r {
view.superview?.layer.cornerRadius = r
}
}
}
I don't know why It does't work on my Simulator with iOS11.1 until trying setting backgroundColor.I added function viewWillTransition for rotating device.
override func viewDidAppear(_ animated: Bool) {
view.superview?.backgroundColor = UIColor.white
view.superview?.layer.cornerRadius = 0
super.viewDidAppear(animated)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
view.superview?.backgroundColor = UIColor.white
view.superview?.layer.cornerRadius = 0
super.viewWillTransition(to: size, with: coordinator)
}
For Swift 4, in case you present your popover embedded in a navigation controller:
override func viewDidAppear(_ animated: Bool) {
navigationController?.view.superview?.layer.cornerRadius = 0
super.viewDidAppear(animated)
}
That's the way I do it in order to achieve such a functionality which works also on device rotation:
In the view controller that you set as a popover add the following code:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
view.superview?.layer.cornerRadius = 0
}
extension MyPopoverViewController: UIPopoverPresentationControllerDelegate {
func popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>, in view: AutoreleasingUnsafeMutablePointer<UIView>) {
self.view.setNeedsLayout()
}
Finally, set your popover's delegate to the instance of this view controller before presenting it:
...
popover.delegate = myPopoverViewController
present(myPopoverViewController, animated: true)
Many of these answers fix the OP's question about corner radius, but you can get the same effect by changing your presented view controller's top view's background to clear. Then just round the corners of the next top view to the desired amount (or not, if you want it to be a straight top).
I prefer this way because it gives you more WYSIWYG control over the presented view. For example, you can present a floating square by just centering a view in the view controller and presenting it.
Related
I am trying to recreate the bottom drawer functionality seen in Maps or Siri Shortcuts by using a UIPresentationController by having it recognise user input and updating the frameOfPresentedViewInContainerView accordingly. However I want this mechanism to work independently of the presented UIViewController as much as possible so I'm trying to have the presentation controller add a handle area above the view. Ideally the view of the presented controller and the handle are should both recognise user input.
This works for the presented view, however any view I add to it responds to no UIGestureRecognizer at all. Am I missing something?
class PresentationController: UIPresentationController {
private let handleArea: UIView = UIView()
override var frameOfPresentedViewInContainerView: CGRect {
// Return some frame for now
return CGRect(x: 0, y: 250, width: containerView!.frame.width, height: 500)
}
override func presentationTransitionWillBegin() {
// Unwrap presented view
guard let presentedView = self.presentedView else {
return
}
// Set color
self.handleArea.backgroundColor = UIColor.green
// Add to view hierachy
presentedView.addSubview(self.handleArea)
// Set constraints
self.handleArea.trailingAnchor.constraint(equalTo: presentedView.trailingAnchor).isActive = true
self.handleArea.leadingAnchor.constraint(equalTo: presentedView.leadingAnchor).isActive = true
self.handleArea.bottomAnchor.constraint(equalTo: presentedView.topAnchor).isActive = true
self.handleArea.heightAnchor.constraint(equalToConstant: 56).isActive = true
self.handleArea.translatesAutoresizingMaskIntoConstraints = false
// These don't help
self.handleArea.isUserInteractionEnabled = true
presentedView.isUserInteractionEnabled = true
presentedView.bringSubviewToFront(self.handleArea)
}
override func presentationTransitionDidEnd(_ completed: Bool) {
if completed {
// Add gesture recognizer
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.onHandleAreaTapped(sender:)))
self.handleArea.addGestureRecognizer(tapGestureRecognizer)
}
}
override func dismissalTransitionDidEnd(_ completed: Bool) {
// Remove subview
self.handleArea.removeFromSuperview()
}
// MARK: - Responder
#objc private func onHandleAreaTapped(sender: UITapGestureRecognizer) {
print("tap") // No output
}
}
I managed to solve it by adding both the handle area and the view of the presentedViewController to a custom view and then overriding the presentedView property and returning my custom view.
I'm new to swift and Xcode so there may be a simple answer or better way to do this. I am trying to make rounded edges to my buttons in all size iOS devices and the best way that I have found so far is using this method:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for i in 0..<buttons.count {
self.buttons[i].layer.cornerRadius = self.buttons[i].bounds.size.height / 2
}
examResultsBackButtonOutlet.layer.cornerRadius = examResultsBackButtonOutlet.bounds.size.height / 2
}
// Build UI
func loadUI() {
// Round the edges, change color background, font
titleOutlet.mainMenuStyle("", 75, earthGreenWithAlpha)
for i in 0..<buttons.count {
buttons[i].mainMenuStyle("", 40)
}
print("Main Menu successful load")
}
I call the loadUI() method in my viewDidLoad method for what its worth:
//MARK: viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
// Load the UI
loadUI()
loadExamResultsView()
mainMenu()
examResultsStackView.isHidden = true
loadStoredScores()
loadStoredSettings()
}
The issue presents itself not when I start the app for the first time (this is the first scene/View Controller), rather the issue occurs when I segue back to this scene from my second View Controller.
What happens is the buttons are formatted weird for about a half a second, then they are reformatted perfectly:
Correctly formatted screenshot
Incorrectly formatted screenshot
You'll notice the second image (which only occurs for about 0.5 seconds) has much more intense corner radius'.
How can I prevent this from happening so that the user only sees the perfectly round corner radius' for each button and not the poor janky ones?
Thanks!
Thanks for everyone's feedback but here is what fixed it for me:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
for i in 0..<buttons.count {
self.buttons[i].layer.cornerRadius = self.buttons[i].bounds.size.height / 2
}
examResultsBackButtonOutlet.layer.cornerRadius = examResultsBackButtonOutlet.bounds.size.height / 2
}
This replaced the original post:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for i in 0..<buttons.count {
self.buttons[i].layer.cornerRadius = self.buttons[i].bounds.size.height / 2
}
examResultsBackButtonOutlet.layer.cornerRadius = examResultsBackButtonOutlet.bounds.size.height / 2 }
Thanks!
Maybe you can try this:
https://imgur.com/a9kTt98
or add to extension of UIView
extension UIView {
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = (newValue > 0)
}
}
}
I want a popover without rounded corners and with no arrow.
I have done the following code but it did not work:
//SerachPopViewController.swift
//MARK: InitCoder
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//popover settings
//popoverPresentationController!.permittedArrowDirections = .Any
modalPresentationStyle = .Popover
popoverPresentationController!.delegate = self
//permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
self.preferredContentSize = CGSize(width:340,height:380)
}
//QueryTableViewController.swift
#IBAction func searchFilter(sender: AnyObject) {
let searchPopController = storyboard!.instantiateViewControllerWithIdentifier("SerachPopViewController") as! SerachPopViewController
searchPopController.serachPopDelegate = self
searchPopController.modalPresentationStyle = .Popover
searchPopController.preferredContentSize = CGSize(width:340,height:380)
let popoverPresentationController = searchPopController.popoverPresentationController
popoverPresentationController!.sourceView = self.view;
popoverPresentationController!.sourceRect = CGRectMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds),0,0)
popoverPresentationController!.permittedArrowDirections = UIPopoverArrowDirection();
self.presentViewController(searchPopController, animated: true, completion: nil)
}
I am able to display popover view with arrow and rounded arrow.
Please help me to achieve:
popup view with rectangle corner
popup view without direction arrows
Using the concept above, you can also set the corner radius in the completion parameter.
Swift 3
let popoverViewController: UIViewController = // Some view controller to be presented in a popover
// Set popover properties here...
// i.e. popoverViewController.modalPresentationStyle = .popover
present(popoverViewController, animated: true, completion: {
popoverViewController.view.superview?.layer.cornerRadius = 0
// Additional code here
})
In iOS 11 its not possible to use #SHN solution for removing rounded corners. The corner radius is set to default value after viewWillAppear.
Radius must be set in viewDidAppear method
override func viewDidAppear(_ animated: Bool) {
view.superview?.layer.cornerRadius = 0
super.viewDidAppear(animated)
}
To get popover without arrow when you are initiating popover, use:
popover!.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
For popover without corner radius, in the popover content view controller use:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Do any additional setup after loading the view, typically from a nib.
self.view.superview?.layer.cornerRadius = 0.0;
}
I was not 100% happy with shawnynicole's answer because I realized the change from the rounded to the rectangular corners are notable/visible.
So I came up with this: subclass the view controller (in my case it was an UINavigationController) and override viewDidLayoutSubviews and update corners there. This is better because corners change animation is not visible and it will be updated every time needed (on rotations, etc).
It works in iOS11 and probably should work on others versions too.
class PopoverNavigationController: UINavigationController {
#IBInspectable var cornerRadius: Int = -1
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let r = CGFloat(cornerRadius)
if r >= 0 && view.superview?.layer.cornerRadius != r {
view.superview?.layer.cornerRadius = r
}
}
}
I don't know why It does't work on my Simulator with iOS11.1 until trying setting backgroundColor.I added function viewWillTransition for rotating device.
override func viewDidAppear(_ animated: Bool) {
view.superview?.backgroundColor = UIColor.white
view.superview?.layer.cornerRadius = 0
super.viewDidAppear(animated)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
view.superview?.backgroundColor = UIColor.white
view.superview?.layer.cornerRadius = 0
super.viewWillTransition(to: size, with: coordinator)
}
For Swift 4, in case you present your popover embedded in a navigation controller:
override func viewDidAppear(_ animated: Bool) {
navigationController?.view.superview?.layer.cornerRadius = 0
super.viewDidAppear(animated)
}
That's the way I do it in order to achieve such a functionality which works also on device rotation:
In the view controller that you set as a popover add the following code:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
view.superview?.layer.cornerRadius = 0
}
extension MyPopoverViewController: UIPopoverPresentationControllerDelegate {
func popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>, in view: AutoreleasingUnsafeMutablePointer<UIView>) {
self.view.setNeedsLayout()
}
Finally, set your popover's delegate to the instance of this view controller before presenting it:
...
popover.delegate = myPopoverViewController
present(myPopoverViewController, animated: true)
Many of these answers fix the OP's question about corner radius, but you can get the same effect by changing your presented view controller's top view's background to clear. Then just round the corners of the next top view to the desired amount (or not, if you want it to be a straight top).
I prefer this way because it gives you more WYSIWYG control over the presented view. For example, you can present a floating square by just centering a view in the view controller and presenting it.
I'm trying to implement 6 lines high description label and I want it to be focusable. Ideally that would mean extending UILabel class to make a custom component. I tried that by implementing canBecomeFocused and didUpdateFocusInContext but my UILabel doesn't seem to get any focus.
I also tried replacing UILabel with UIButton, but buttons aren't really optimised for this sort of thing. Also that would mean I'd need to change buttonType on focus from custom to plain.. and buttonType seems to be a ready-only property.
In reality I'd like to have exact same text label implemented by Apple in Apple TV Movies app. For movie description they have a text label that displays a few lines of text and a "more". When focused it looks like a button (shadows around) and changed background color. When tapped - it opens up a modal window with entire movie description.
Any suggestions? Or maybe someone has already implemented this custom control for tvOS? Or event better - there is a component from Apple that does this and I'm missing something.
P.S: Swift solution would be welcome :)
Ok, answering my own question :)
So it appears that some some views are "focusable" on tvOS out-of-the-box, and other have to be instructed to do so.
I finally ended up using UITextView, which has a selectable property, but if not one of these focusable views by default. Editing of TextView has to be disabled to make it look like UILabel. Also, currently there is a bug which prevents you from using selectable property from Interface Builder but works from code.
Naturally, canBecomeFocused() and didUpdateFocusInContext had to be implemented too. You'll also need to pass a UIViewController because UITextView is not capable of presenting a modal view controller. Bellow is what I ended up creating.
class FocusableText: UITextView {
var data: String?
var parentView: UIViewController?
override func awakeFromNib() {
super.awakeFromNib()
let tap = UITapGestureRecognizer(target: self, action: "tapped:")
tap.allowedPressTypes = [NSNumber(integer: UIPressType.Select.rawValue)]
self.addGestureRecognizer(tap)
}
func tapped(gesture: UITapGestureRecognizer) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let descriptionView = storyboard.instantiateViewControllerWithIdentifier("descriptionView") as? DescriptionViewController {
if let view = parentView {
if let show = show {
descriptionView.descriptionText = self.data
view.modalPresentationStyle = UIModalPresentationStyle.OverFullScreen
view.presentViewController(descriptionView, animated: true, completion: nil)
}
}
}
}
override func canBecomeFocused() -> Bool {
return true
}
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
if context.nextFocusedView == self {
coordinator.addCoordinatedAnimations({ () -> Void in
self.layer.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.2).CGColor
}, completion: nil)
} else if context.previouslyFocusedView == self {
coordinator.addCoordinatedAnimations({ () -> Void in
self.layer.backgroundColor = UIColor.clearColor().CGColor
}, completion: nil)
}
}
}
As for making a UILabel focusable:
class MyLabel: UILabel {
override var canBecomeFocused: Bool {
return true
}
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
super.didUpdateFocus(in: context, with: coordinator)
backgroundColor = context.nextFocusedView == self ? .blue:.red
}
}
IMPORTANT!!!
As stated on the apple developer portal:
The value of this property is true if the view can become focused; false otherwise.
By default, the value of this property is false. This property informs the focus engine if a view is capable of being focused. Sometimes even if a view returns true, a view may not be focusable for the following reasons:
The view is hidden.
The view has alpha set to 0.
The view has userInteractionEnabled set to false.
The view is not currently in the view hierarchy.
Use a collection view with just one cell and add transform to cell and change cell background color in didUpdateFocusInContext when focus moves to cell.
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
coordinator.addCoordinatedAnimations({
if self.focused {
self.transform = CGAffineTransformMakeScale(1.01, 1.01)
self.backgroundColor = UIColor.whiteColor()
self.textLabel.textColor = .blackColor()
}
else {
self.transform = CGAffineTransformMakeScale(1, 1)
self.backgroundColor = UIColor.clearColor()
self.textLabel.textColor = .whiteColor()
}
}, completion: nil)
}
As an additional step you could try to extract the color of the image if you are using the image as background like iTunes and use that for Visual effect view behind the cell.
Also you can apply transform to the collectionView in the video controller to make it look like in focus
You can use system button, and set the background image in storyboard to an image that contains the color you would like
I am trying to animate the root-view-controller-change in my app. After I swap the view controllers, I load the data necessary for the 2nd controller right away. While the data is loading, I show a loader(MBProgressHUD). This is my function for swapping the view controllers:
class ViewUtils {
class func animateRootViewController(duration: NSTimeInterval, changeToViewController: UIViewController) {
let window = UIApplication.sharedApplication().delegate?.window?
if window == nil {
return
}
UIView.transitionWithView(window!,
duration: duration,
options: UIViewAnimationOptions.TransitionFlipFromLeft | UIViewAnimationOptions.AllowAnimatedContent,
animations: {
window!.rootViewController = changeToViewController
},
completion: nil
)
}
}
All good with this but one thing - it totally breaks the loader. I am attaching an imagine of what's happening:
This is the 2nd view controller while rotating. Once the rotation is complete, the loader appears just fine, both the spinner and the text tween to the correct position in the rounded rectangle.
I really don't understand why this happens, would somebody explain it to me, please? Is there a way to prevent it?
The code of the 2nd view controller where I show the loader:
override func viewDidLoad() {
super.viewDidLoad()
hud = HUD(containingView: view)
hud.show()
createBackground()
}
And my hud class:
class HUD {
private var hudBG: UIView!
private var view: UIView!
private(set) var isShown = false
init(containingView: UIView) {
view = containingView
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func show() {
if !isShown {
if(hudBG == nil) {
hudBG = UIView(frame: CGRectMake(0, 0, view.bounds.width, view.bounds.height))
hudBG.backgroundColor = UIColor(white: 0, alpha: 0.4)
}
view.addSubview(hudBG)
let hud = MBProgressHUD.showHUDAddedTo(view, animated: true)
hud.mode = MBProgressHUDModeIndeterminate
hud.labelText = "Cargando"
hudBG.alpha = 0
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.hudBG.alpha = 1
})
isShown = true
}
}
func hide() {
if isShown {
UIView.animateWithDuration(0.3, animations: {
() -> Void in
self.hudBG.alpha = 0
}, completion: {
(b) -> Void in
self.hudBG.removeFromSuperview()
})
MBProgressHUD.hideHUDForView(view, animated: true)
isShown = false
}
}
}
Thanks a lot for any ideas!
You are adding the hud to a view that is not properly initialized yet.
If you are loading the view controller from a xib or storyboard, the view and it's subviews have the size as they were loaded from interface.
You have to add the hud after the views have been resized to their final size.
If you move
hud = HUD(containingView: view)
hud.show()
to viewDidLayoutSubviews, it should work fine.
I noticed a similar problem when moving an app from iOS 7 to iOS 8. During animations, especially when scaling was involved, the view positions got distorted.
I am pretty sure it's a bug. The simplest workaround is to animate only screenshots or view snapshots, not actual views - it's more work and you can't have views animating when the main animation is in progress but in general it's a more stable solution.