How to implement the behaviour seen in the iOS lock screen when touching the camera icon? - objective-c

I'm trying to implement the behaviour seen in the iOS lock screen when touching the camera icon, using a UIPageViewController
I have a UIPageViewController that vertically scrolls between 2 view controllers. I would like to do a small "bounce/bump" when the user taps the view.
Any ideas on how I can accomplish this?
Thank you!!

Thanks to #Szu for pointing me out to UIKit Dynamics :)
Here's a rough solution:
Call init with a controller's view (to be bounced) and a reference view (your current view controller's view property).
eg.
FSBounceAnimator(contentView: viewController.view, referenceView: self.view)
viewController is any viewController that you may instantiated and added as a childViewController
self.view is your current viewController view.
Call bounce(), to bounce :)
import UIKit
class FSBounceAnimator: NSObject {
var animator: UIDynamicAnimator
var gravityBehaviour: UIGravityBehavior
var pushBehavior: UIPushBehavior
var itemBehaviour: UIDynamicItemBehavior
init(contentView: UIView!, referenceView: UIView!) {
animator = UIDynamicAnimator(referenceView: referenceView)
var colisionBehaviour: UICollisionBehavior = UICollisionBehavior(items: [contentView])
colisionBehaviour.setTranslatesReferenceBoundsIntoBoundaryWithInsets(UIEdgeInsetsMake(-100, 0, 0, 0))
animator.addBehavior(colisionBehaviour)
gravityBehaviour = UIGravityBehavior(items: [contentView])
gravityBehaviour.gravityDirection = CGVectorMake(1, 1)
animator.addBehavior(gravityBehaviour)
pushBehavior = UIPushBehavior(items: [contentView], mode: UIPushBehaviorMode.Instantaneous)
pushBehavior.magnitude = 0.0
pushBehavior.angle = 0.0
animator.addBehavior(pushBehavior)
itemBehaviour = UIDynamicItemBehavior(items: [contentView])
itemBehaviour.elasticity = 0.45
animator.addBehavior(itemBehaviour)
super.init()
}
func bounce() {
self.pushBehavior.pushDirection = CGVectorMake(0.0, 100.0);
self.pushBehavior.active = true;
}
}

Related

Touch and move CAShapeLayer over UIImageView

I am trying to custom annotate images on image view using CAShapeLayers. This include adding shapes(like rectangle, ellipse, line) over the image view, touch them, move them around and pinch-zoom them. I am able to recognise the touch of the layer over the image view, but unsuccessful when tried to move it around. As I try to move the layer, it flies to a corner.
Code and output as below:
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func drawRectangle(_ sender: UIButton) { // to initially place the shape at the centre of the image view.
drawRectangleWithRect(CGRect(x: imageView.bounds.midX - 100, y: imageView.bounds.midY - 100, width: 200, height: 200))
}
func drawRectangleWithRect(_ rect: CGRect) {
let path = UIBezierPath(rect: rect)
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
//shapeLayer.bounds = (shapeLayer.path?.boundingBox)!
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.fillColor = nil
shapeLayer.lineWidth = 1
imageView.layer.addSublayer(shapeLayer)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let point = touch!.location(in: imageView)
guard let sublayers = imageView.layer.sublayers else { return }
for sublayer in sublayers {
if let sublayer = sublayer as? CAShapeLayer, let path = sublayer.path {
//if path.contains(point) {
sublayer.position = point
//}
}
}
}
}
Tapping on Rectangle button creates the layer at the centre of image view as above. But when I start to touch and move the layer just a bit it goes off to the right bottom corner and disappears.
Now tweaked a bit - uncommented the line shapeLayer.bounds = (shapeLayer.path?.boundingBox)! inside the method drawRectangleWithRect(_ rect: CGRect). Then tapping on the Rectangle button placed the layer at the left top of the view and not at the centre :( . But moving it around now made the layer move around with my finger (though there is a delay). But also that this made the layer move even when image view is touched outside the layer. I understand that some if condition inside the touchesMoved method is needed that properly recognises the layer area but the commented one in the above code did not work properly.
What am I missing to properly place the layer at centre of the view and move it around without any delay ?
Can I actually pinch zoom the layers effectively ?
What is the best recommended way to annotate images like this ? Adding layers with touch recognition or adding shapes as subviews with pan gestures ?

UISearchController background not matching UINavigationBar background in iOS 11

I'm updating an older app to support iPhone X and when updating a searchable UITableViewController the background of the UISearchController doesn't change when placing it in the navigation bar.
I'm using the following code to place it in the navigation bar:
self.navigationItem.searchController = self.searchController;
self.navigationItem.hidesSearchBarWhenScrolling = NO;
Inspecting the Interface shows the following hierarchy:
_UIBarBackground contained within the _UINavigationControllerManagedSearchPalette remains white.
Is there something I've missed when creating the UINavigationController?
Try to apply background color same as the navigationBar tint color.
searchBar.tintColor = UIColor.white
searchBar.backgroundColor = UIColor.red
searchBar.clearBackgroundColor()
Remove color of the searchBar by this way:
extension UISearchBar {
func clearBackgroundColor() {
guard let UISearchBarBackground: AnyClass = NSClassFromString("UISearchBarBackground") else { return }
for view in self.subviews {
for subview in view.subviews {
if subview.isKind(of: UISearchBarBackground) {
subview.alpha = 0
}
}
}
}
}
with above code I am able to achieve this output
Hope this help.

In OS X app, how to use auto layout when a window is created programmatically?

My OS X App tries to create a window programmatically as below (suggested by another SO question I saw earlier):
class MyViewController: NSViewController {
override func loadView() {
let view = NSView(frame: NSMakeRect(0,0,300,100))
view.wantsLayer = true
view.layer?.borderWidth = 2
view.layer?.borderColor = NSColor.red.cgColor
self.view = view
}
}
let viewController = MyViewController()
let window = NSWindow(contentRect: someFrame, styleMask: [.resizable, .closable, .miniaturizable, .titled], backing: NSBackingStoreType.buffered, defer: false)
window.contentView?.addSubview(viewController.view)
window.makeKeyAndOrderFront(nil)
I can successfully create windows by the code above. However, my question is... how to make the view created in loadView inside the view controller to be the same size as the window's content view?
Right now, it creates the view with fixed numbers (0, 0, 300, 100). How to make this view to be the same size as the window by using Auto layout, so when the window is resized, this view will resize automatically too?
One would thing that Apple would document such a thing. And indeed they did:
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html

UIPopoverPresentationController has wrong anchor position on iPhone 6 plus and 7 plus when it's in landscape mode [duplicate]

In my iOS 8 app, this popover segue appears correctly on all devices in all orientations except for iPhone 6 Plus landscape:
This is how it looks on iPhone 6 Plus landscape (it is stretching almost from top to bottom):
And when it displays like this, clicking outside of the view doesn't dismiss it (although Cancel does work). Rotating back to portrait gets it back to normal.
All of the constraints in this UIViewController are installed on all size classes.
When debugging values in viewDidAppear: I see the following:
po self.view: frame = (0 0; 250 394)
po self.preferredContentSize (width = 250, height = 160)
What is causing the view's height to jump to 394?
I'm actually having the same issue with another popover segue in iPhone 6 Plus landscape as well. (And in case there was curiosity, I'm using a VC instead of 'UIAlertController' here because of the validation requirements of the UITextField displayed don't work well with UIAlertController.)
Edit to include my popover code:
This code is found in prepareForSegue:
FavoriteNameViewController *nameVC = segue.destinationViewController;
UIPopoverPresentationController *popPC = nameVC.popoverPresentationController;
popPC.delegate = self;
nameVC.delegate = self;
nameVC.view.center = self.originalContentView.center;
And then the delegate method:
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller {
return UIModalPresentationNone;
}
And here is the segue definition in Xcode:
What you're seeing is not a popover. It's a normal presented view. By default, a popover appears as a popover on iPad, but as a presented view on iPhone — including the iPhone 6 plus. On other iPhones, this presented view is fullscreen - it covers everything. But the iPhone 6 is so wide that they don't do that, so it appears in the middle of the screen at a standard width (the width of a smaller iPhone).
Thus, the preferred content size has no effect. This isn't a popover. Presented view controller views are given a standard size, and this one is no exception.
However, you can have the popover appear as a popover on iPhone. To do so:
Set a delegate for the popover view controller's popoverPresentationController before presenting it.
In the delegate, implement adaptivePresentationStyleForPresentationController: and return .None.
However, this is apparently not working on iPhone 6 Plus in landscape mode; the popover is not "adapting". I would describe this as a bug!
EDIT In iOS 9, the problem is solved! Implement the new delegate method adaptivePresentationStyleForPresentationController:traitCollection: to return .None and you'll get a popover under all circumstances, including the iPhone 6 Plus in landscape. Here's a complete working example where the popover is created and summoned in code in response to a button tap:
#IBAction func doButton(sender: AnyObject) {
let vc = MyViewController()
vc.preferredContentSize = CGSizeMake(400,500)
vc.modalPresentationStyle = .Popover
if let pres = vc.presentationController {
pres.delegate = self
}
self.presentViewController(vc, animated: true, completion: nil)
if let pop = vc.popoverPresentationController {
pop.sourceView = (sender as! UIView)
pop.sourceRect = (sender as! UIView).bounds
}
}
func adaptivePresentationStyleForPresentationController(
controller: UIPresentationController,
traitCollection: UITraitCollection)
-> UIModalPresentationStyle {
return .None
}

Intercepting UIScrollView Scrolls

I have a UIScrollView and a UITableView inside the UIScrollView.
I would like to intercept scrolling of the UITableView and only allow scrolling if the super view (UIScrollView) have reached a specific contentOffset.
I have created subclass of both UIScrollView and UITableView, how do i catch scrolling event and intercepting the scrolling while the user is still scrolling?
Example of what i'm trying to accomplish:
The UITableView is going to have a header, if i scroll down the header will collapse to 30% of original size and and stay visible at the top. After i have scrolled back to the top of the UITableView i want the header to expand. In other word i want to extend the scrolling of a UITableView with a header that can collapse/expand.
There might be better way to accomplish this, i'm open for suggestions.
There may be a better way, but this solution is working for me.
UITableView (let's call it innerScrollView) inside of UIScrollView (let's call it scrollView) inside whatever the main view is (let's call it view).
Set an outlet in your view controller for the innerScrollView (self.innerScrollView).
Set bounce in XIB or code to: scrollView.bounces = YES and innerScrollView.bounces = NO.
Set scrolling to disabled initially for the innerScrollView.
In code (viewDidLoad:) set scrollView.contentSize.height to view.frame.size.height + the 70% of the header you want to disappear. Resize innerScrollView to fill the resized contentView to the bottom.
Set your view controller as the delegate for scrollView and implement scrollViewDidScroll: as follows:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat maxYOffset = self.scrollView.contentSize.height - self.scrollView.frame.size.height;
CGFloat minInnerYOffset = self.innerScrollView.contentInset.top;
CGFloat currentYOffset = scrollView.contentOffset.y;
CGFloat newYOffsetDelta = currentYOffset - maxYOffset;
CGFloat currentInnerYOffset = self.innerScrollView.contentOffset.y;
if (scrollView.contentOffset.y >= maxYOffset) {
self.innerScrollView.scrollEnabled = YES;
self.scrollView.contentOffset = CGPointMake(0, maxYOffset);
if (currentYOffset != maxYOffset) {
self.innerScrollView.contentOffset = CGPointMake(0, currentInnerYOffset + newYOffsetDelta);
}
}
else if (currentInnerYOffset > minInnerYOffset) {
self.scrollView.contentOffset = CGPointMake(0, maxYOffset);
self.innerScrollView.contentOffset = CGPointMake(0, currentInnerYOffset + newYOffsetDelta);
}
else if (currentYOffset < scrollView.contentInset.top) {
scrollView.contentOffset = CGPointMake(0, scrollView.contentInset.top);
}
else {
self.lowerScrollView.scrollEnabled = NO;
}
}
I just whipped this up and quickly tested it. It all seems to work, but there may be some improvements and tweaks needed. Regardless, this should get you started at least.
Well, you should use the following UIScrollViewDelegate method
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
// here perform the action you need
}