Presenting modal in iOS 13 with custom height - objective-c

In iOS 13 there is a new behaviour for modal view controller when being presented.And I found the build-in App Photo presents a smaller model view controller.
How can I present a viewController with a custom size like this,and can slide up to a larger height?
Picture screenshots from system photo app.

Yes it is possible Presenting modal in iOS 13 with custom height.
You just need to add the below code into your Presenting modal
override func updateViewConstraints() {
self.view.frame.size.height = UIScreen.main.bounds.height - 150
self.view.frame.origin.y = 150
self.view.roundCorners(corners: [.topLeft, .topRight], radius: 10.0)
super.updateViewConstraints()
}
extension UIView {
func roundCorners(corners: UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
}

Answer in Swift
I was looking for a way to replicate that type of ViewController behaviour, albeit with basic UI and have found a rather simple solution.
Basically, you create a ViewController (CardViewContoller) with a transparent background and then add to it a card-like view with a UIPanGestureReconizer, that will enable you to drag it around and dismiss it with the ViewController.
To present you simply call present, setting the modalPresentationStyle to .overCurrentContext and modalTransitionStyle to .coverVertical:
let cardVC = CardViewController()
cardVC.modalPresentationStyle = .overCurrentContext
cardVC.modalTransitionStyle = .coverVertical
present(cardVC, animated: true, completion: nil)
The in CardViewController, which can be created programmatically or using Interface Builder, you add a UIPanGestureRecognizer to your card view (contentView):
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handleDismiss(recognizer:)))
panGestureRecognizer.cancelsTouchesInView = false
contentView.addGestureRecognizer(panGestureRecognizer)
Then just add an #objc function that will respond to the UIPanGestureRecognizer:
#objc
func handleDismiss (recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .changed:
viewTranslation = recognizer.translation(in: view)
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
guard self.viewTranslation.y > 0 else {return}
self.view.transform = CGAffineTransform(translationX: 0, y: self.viewTranslation.y)
})
case .ended:
if viewTranslation.y < swipeThreshold {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.view.transform = .identity
})
} else {
dismiss(animated: true, completion: nil)
}
default:
break
}
}
The swipeThreshold is a CGFloat variable with a value of your choosing (200 works great for me), that if the UIPanGestureRecognizer y translation exceeds, will trigger the dismissal of the ViewController along with all the elements.
Likewise, you can add a simple button that will dismiss the ViewController on .touchUpInside calling dismiss()
If you want, you can have a look at this repo, in which I have a sample project that exhibits this behaviour. That way you can build your own totally customisable cards.

Related

ios 10 Snapshotting a view that has not been rendered results in an empty snapshot

this question ask again but i dont find for ios 10
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera)
{
self.imagePicker.delegate = self
self.imagePicker.sourceType = UIImagePickerControllerSourceType.camera;
self.imagePicker.allowsEditing = false
self.imagePicker.cameraCaptureMode = .photo
//self.imagePicker.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
self.present(self.imagePicker, animated: true, completion: nil)
self.imagePicked.isUserInteractionEnabled = true
}
else
{
print("No Camera")
}
Snapshotting a view that has not been rendered results in an empty
snapshot.Ensure your view has been rendered at least once before
snapshotting or snapshot after screen updates.
when i rotate the camera and take a shot than this error occurs.
Self Solution Working for me like charm :-) hope its helpful for all
DispatchQueue.global(qos: .userInitiated).async
{
self.present(self.imagePicker, animated: true, completion: nil)
}
I got the error
This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes...
Using DispatchQueue.main.async instead works for me.
This behavior is not limited to UIImagePickerController. Below is an example of a UIViewController which presents another UIViewController modally. In the second UIViewController, Safari is launched to present a URL, thus triggering the same error message, "Cannot snapshot view (>) with afterScreenUpdates:NO, because the view is not in a window. Use afterScreenUpdates:YES."
I haven't yet found any way of suppressing the message, but in my app it does no harm. I think what's going on here is that some Apple code is taking a snapshot of the app's view hierarchy, but the keyboard (which is owned by a separate UIWIndow) has not been rendered before the snapshot is taken.
/* Generates the error message:
Cannot snapshot view (<UIKeyboardImpl: 0x7f82ded12ea0; frame = (0 0; 414 271); layer = <CALayer: 0x610000035e20>>) with afterScreenUpdates:NO, because the view is not in a window. Use afterScreenUpdates:YES.
... after the "Now Tap Me, For Glory!" label is clicked
*/
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let input = UITextField()
input.placeholder = "Tap here first -- to bring up keyboard"
input.frame = CGRect(x: 10, y: 50, width: 300, height: 20)
view.addSubview(input)
let button = UIButton()
button.setTitleColor(UIColor.blue, for: .normal)
button.setTitle("Then tap here", for: .normal)
button.addTarget(self,
action: #selector(buttonPushed),
for: .touchUpInside)
button.frame = CGRect(x: 10, y: 80, width: 200, height: 20)
view.addSubview(button)
}
func buttonPushed() {
let modalVC = ModalViewController()
present(modalVC, animated: true, completion: nil)
}
}
class ModalViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
let label = UILabel()
label.text = "Now Tap Me, For Glory!"
label.frame = CGRect(x: 10, y: 50, width: 300, height: 20)
view.addSubview(label)
label.isUserInteractionEnabled = true
label.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(labelTapped)))
}
func labelTapped() {
UIApplication.shared.open(URL(string: "http://planetbeagle.com")!, options: [:], completionHandler: { _ in
self.dismiss(animated: true, completion: nil) })
}
}

Swift 3 - custom segue crashed with UIStoryboardSegueTemplate.m:85

According to this link, I am trying to make my own custom segue.
In the initial view controller (titled: First), pressing 2 (UIButton) to segue to Second.
But the app always crashes at performSegue(withIdentifier: "customSegue", sender: self) with error: *** Assertion failure in -[UIStoryboardSegueTemplate segueWithDestinationViewController:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3599.6/UIStoryboardSegueTemplate.m:85
Here is the main storyboard:
Crash:
CustomSegue
import UIKit
class CustomSegue: UIStoryboardSegue {
override func perform() {
let sourceView = source.view!
let destView = destination.view!
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
destView.frame = CGRect(x: 0, y: screenHeight, width: screenWidth, height: screenHeight)
let window = UIApplication.shared.keyWindow
window?.insertSubview(destView, aboveSubview: sourceView)
UIView.animate(withDuration: 0.4, animations: {
sourceView.frame = sourceView.frame.offsetBy(dx: 0.0, dy: -screenHeight)
destView.frame = destView.frame.offsetBy(dx: 0.0, dy: -screenHeight)
}) { [weak self] (finished) in
guard let strongSelf = self else { return }
strongSelf.source.present(strongSelf.destination, animated: false, completion: nil)
}
}
}
Second is just an empty View Controller:
import UIKit
class Second: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
I compare the setup in my project with the downloaded sample but cannot find the difference.
How should I fix this crash?
For those who are interested.
It took me nearly 2 days to find this: to overcome this crash, I have to specify "Module" in Storyboard - click on Segue - Attribute inspector.

Repositioning UIPopoverController on device rotation

I'm trying to re-position a popover view when the device is rotated. I use this code to create the popover:
settingsViewCtrl = storyboard.instantiateViewControllerWithIdentifier("settingsViewCtrl") as SettingsViewCtrl;
settingsPopoverCtrl = UIPopoverController(contentViewController: settingsViewCtrl);
settingsPopoverCtrl.delegate = self;
let m:CGFloat = min(view.frame.width, view.frame.height);
let s:CGSize = CGSizeMake(m - 100, m - 100);
let r:CGRect = CGRectMake(view.frame.width * 0.5, view.frame.height * 0.5, 1, 1);
settingsPopoverCtrl.setPopoverContentSize(s, animated: true);
settingsPopoverCtrl.presentPopoverFromRect(r, inView: view, permittedArrowDirections: nil, animated: true);
And I use willRepositionPopoverToRect in my delegate ...
func popoverController(popoverController:UIPopoverController!, willRepositionPopoverToRect rect:UnsafeMutablePointer<CGRect>, inView view:AutoreleasingUnsafeMutablePointer<UIView?>)
{
if (settingsPopoverCtrl != nil && settingsViewCtrl != nil)
{
let r:CGRect = CGRectMake(self.view.frame.width * 0.5, self.view.frame.height * 0.5, 1, 1);
settingsPopoverCtrl.presentPopoverFromRect(r, inView: self.view, permittedArrowDirections: nil, animated: true);
}
}
But that can't be it since when I rotate the device I'm getting the warning:
Application tried to represent an active popover presentation:
and the popover also isn't repositioned.
How do I get to apply and update positioning CGRect in willRepositionPopoverToRect so that the popover view updates? Would it be possible to modify the provided rect:UnsafeMutablePointer<CGRect> and give it back to the popover?
UPDATE:
According to the docs (https://developer.apple.com/library/ios/documentation/uikit/reference/UIPopoverControllerDelegate_protocol/Reference/Reference.html#//apple_ref/occ/intfm/UIPopoverControllerDelegate/popoverController%3awillRepositionPopoverToRect%3ainView%3a):
If you want to propose a different rectangle for the popover, put the
new value in this parameter.
So the question is: How do I put the new value into this rect?
Let me answer this:
let r:CGRect = CGRectMake(self.view.frame.width * 0.5, self.view.frame.height * 0.5, 1, 1);
rect.initialize(r);
... will obviously init the given rect with new dimensions and it works flawless when changing device rotation, unlike the misleading code on Apple's docs where they tell to use presentPopoverFromRect once again, which totally doesn't work.

Animating only the image in UIBarButtonItem

Ive seen this effect in 2 apps and I REALLY want to find how to do it.
The animation is in a UIBarButtonItem, and is only to the image. The image is a + symbol, and it rotates to a X.
If you want to see the effect you have to start a conversation with someone and next to the text input theres the + button for images and emoji's. Or heres a video of the effect in another app, after he taps the bar button you see it rotate to a X, http://www.youtube.com/watch?v=S8JW7euuNMo.
I have found out how to do the effect but only on a UIImageView, I have to turn off all the autoresizing and the view mode has to be centered, then apply the rotation transform to it. I have tried many ways of trying to have it work in a bar item and so far the best way is adding a image view instance, then setting it up and setting the view mode centered and autoresizing off and then using that image view for a custom bar item view. But when i do this, the effect works except while its doing it, the image will go off to the side a little bit instead of staying where it already is. Ive tried getting the center before the animation and set it during the animation but that doesnt do anything.
So the answer for this is you have to make a instance of the Image view, then set it up with no resizing and view mode is centered. Then add the image view to a UIButton with custom type, and then use the button as the custom view for the bar item.
- (IBAction)animate {
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
imageView.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(45));
} completion:^(BOOL finished) {
imageView.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(0));
if ([imageView.image isEqual:[UIImage imageNamed:#"Add.png"]]) {
imageView.image = [UIImage imageNamed:#"Close.png"];
}
else imageView.image = [UIImage imageNamed:#"Add.png"];
}];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Add.png"]];
imageView.autoresizingMask = UIViewAutoresizingNone;
imageView.contentMode = UIViewContentModeCenter;
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0, 0, 40, 40);
[button addSubview:imageView];
[button addTarget:self action:#selector(animate) forControlEvents:UIControlEventTouchUpInside];
imageView.center = button.center;
barItem = [[UIBarButtonItem alloc] initWithCustomView:button];
navItem.rightBarButtonItem = barItem;
}
Recently had to do the same thing in Swift. I created a tutorial that includes starter and final projects, and goes step-by-step with some tips sprinkled in. The code looks like this:
#IBOutlet weak var rightBarButton: UIBarButtonItem! {
didSet {
let icon = UIImage(named: "star")
let iconSize = CGRect(origin: CGPointZero, size: icon!.size)
let iconButton = UIButton(frame: iconSize)
iconButton.setBackgroundImage(icon, forState: .Normal)
rightBarButton.customView = iconButton
rightBarButton.customView!.transform = CGAffineTransformMakeScale(0, 0)
UIView.animateWithDuration(1.0,
delay: 0.5,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 10,
options: .CurveLinear,
animations: {
self.rightBarButton.customView!.transform = CGAffineTransformIdentity
},
completion: nil
)
iconButton.addTarget(self, action: "tappedRightButton", forControlEvents: .TouchUpInside)
}
}
func tappedRightButton(){
rightBarButton.customView!.transform = CGAffineTransformMakeRotation(CGFloat(M_PI * 6/5))
UIView.animateWithDuration(1.0) {
self.rightBarButton.customView!.transform = CGAffineTransformIdentity
}
}
I wanted to keep the expanded tapping size that the native UIBarButtonItem view provides (such as -initWithBarButtonSystemItem:target:action: versus -initWithCustomView:).
Here's a basic implementation of my code.
- (void)setup {
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(navigationBarRightAction)];
}
- (void)navigationBarRightAction {
UIView *itemView = [self.navigationItem.rightBarButtonItem performSelector:#selector(view)];
UIImageView *imageView = [itemView.subviews firstObject];
if (self.shouldRotate) {
imageView.contentMode = UIViewContentModeCenter;
imageView.autoresizingMask = UIViewAutoresizingNone;
imageView.clipsToBounds = NO;
imageView.transform = CGAffineTransformMakeRotation(M_PI_4);
} else {
imageView.transform = CGAffineTransformIdentity;
}
}
You don't have to use a button as a custom view, it works in fact with less code using a UIImageView and adding a UITapGestureRecognizer.
I hope my solution below helps someone b/c I struggled with this for a long time until I got the bar button item to receive taps and get it to work with all the features I wanted. In my case, I made an "alert bell" bar button item that jingles when there are notifications, and then segues to a new tableview controller when tapped.
This was my solution (Swift 5):
#IBOutlet weak var notifyBell: UIBarButtonItem!
func updateNumNotesAndAnimateBell(_ numNotes: Int) {
guard let image = UIImage(named: "alertBellFill_\(numNotes)") else { return }
let imageView = UIImageView(image: image)
notifyBell.customView = imageView
notifyBell.customView?.contentMode = .center
let tap = UITapGestureRecognizer(target: self, action: #selector(notifyBellPressed))
notifyBell.customView?.addGestureRecognizer(tap)
let scaleTransformA = CGAffineTransform(scaleX: 0.8, y: 0.8)
let rotateTransformA = CGAffineTransform(rotationAngle: 0.0)
let hybridTransformA = scaleTransformA.concatenating(rotateTransformA)
let rotateTransformB = CGAffineTransform(rotationAngle: -1*CGFloat.pi*20.0/180.0)
let hybridTransformB = scaleTransformA.concatenating(rotateTransformB)
notifyBell.customView?.transform = hybridTransformA
UIView.animate(withDuration: 3,
delay: 1,
usingSpringWithDamping: 0.1,
initialSpringVelocity: 10,
options: [.allowUserInteraction, .curveEaseInOut],
animations: {
self.notifyBell.customView?.transform = numNotes > 0 ? hybridTransformB : scaleTransformA
},
completion: nil
)
}
#objc func notifyBellPressed(_ sender: UIBarButtonItem) {
performSegue(withIdentifier: "goToNotificationsTVC", sender: self)
}
Key discoveries for me were that:
-- .allowUserInteraction must be included in the animate options, otherwise the UIBarButtonItem won't be active until the animation completes.
-- You will likely have to declare YourBarButtonItem.customView?.contentMode = .center when using CGAffineTransform(rotationAngle: ) or else it will distort your image when it tries to rotate.
-- The code above includes a scale animation and rotate animation that is different depending on how many notifications I have. With zero notifications, the image is an empty bell, else, it displays the number of notifications in the bell image. I probably could've done this with an updating label, but I had already gone the route of making separate PNGs for each so this worked nicely.

Hide NSWindow title bar

Is there a way to hide the titlebar in an NSWindow? I don't want to have to completely write a new custom window. I can't use NSBorderlessWindowMask because I have a bottom bar on my window, and using NSBorderlessWindowMask makes that disappear. I also tried using setContentBorderThickness:forEdge: with NSMaxYEdge and setting it to 0, that didn't work either.
Any help is appreciated
[yourWindow setStyleMask:NSBorderlessWindowMask];
Starting from OS X 10.10, you can hide title bar.
window1.titlebarAppearsTransparent = true
window1.titleVisibility = .Hidden
Maybe you want to override window style.
window1.styleMask = NSResizableWindowMask
| NSTitledWindowMask
| NSFullSizeContentViewWindowMask
Kind of Welcome screen NSWindow / NSViewController setup (Swift 4.1)
extension NSWindow {
enum Style {
case welcome
}
convenience init(contentRect: CGRect, style: Style) {
switch style {
case .welcome:
let styleMask: NSWindow.StyleMask = [.closable, .titled, .fullSizeContentView]
self.init(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: true)
titlebarAppearsTransparent = true
titleVisibility = .hidden
standardWindowButton(.zoomButton)?.isHidden = true
standardWindowButton(.miniaturizeButton)?.isHidden = true
}
}
}
class WelcomeWindowController: NSWindowController {
private (set) lazy var viewController = WelcomeViewController()
private let contentWindow: NSWindow
init() {
contentWindow = NSWindow(contentRect: CGRect(x: 400, y: 200, width: 800, height: 472), style: .welcome)
super.init(window: contentWindow)
let frameSize = contentWindow.contentRect(forFrameRect: contentWindow.frame).size
viewController.view.setFrameSize(frameSize)
contentWindow.contentViewController = viewController
}
}
class WelcomeViewController: NSViewController {
private lazy var contentView = View()
override func loadView() {
view = contentView
}
init() {
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
contentView.backgroundColor = .white
}
}
class View: NSView {
var backgroundColor: NSColor?
convenience init() {
self.init(frame: NSRect())
}
override func draw(_ dirtyRect: NSRect) {
if let backgroundColor = backgroundColor {
backgroundColor.setFill()
dirtyRect.fill()
} else {
super.draw(dirtyRect)
}
}
}
Result
What happens if you get the superview of the close button? Can you hide that?
// Imagine that 'self' is the NSWindow derived class
NSButton *miniaturizeButton = [self standardWindowButton:NSWindowMiniaturizeButton];
NSView* titleBarView = [miniaturizeButton superview];
[titleBarView setHidden:YES];
The only way I know would be to create a window without a titlebar (see
NSBorderlessWindowMask). Note that you can't (easily) create a window without a
titlebar in IB, so you will have to do a bit of work in code (there are a
couple of different approaches, you can probably figure it out).
A big drawback with using a window without a titlebar is that you're now on the
hook for much more of the standard appearance and behaviour - rounded corners
and such.
I had an experience that when I first set content view of my window and then set the window borderless:
[yourWindow setStyleMask:NSBorderlessWindowMask];
Nothing would appear in my window. So i first set the style mask and after that i've set the content view:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// 1. borderless window
[[self window] setStyleMask: NSBorderlessWindowMask];
// 2. create the master View Controller
self.masterViewController = [[MasterViewController alloc] initWithNibName:#"MasterViewController" bundle:nil];
// 3. Add the view controller to the Window's content view
[self.window.contentView addSubview:self.masterViewController.view];
self.masterViewController.view.frame = ((NSView*)self.window.contentView).bounds;
}
And voila, the content of my window has appeared.
Select Window in storyboard or XIB and tick the red circled option.
You can use WAYInAppStoreWindow available on GitHub which works on Yosemite and Mavericks.
Swift
NSApp.mainWindow?.styleMask = .borderless