Top of UIView isn't positioned well when overlayed in iPhone X - ios

I'm currently overlaying a UIView when a button is pressed on top of a viewController. I have it positioned well in other screen devices and when i run in on iPhone X + devices. Its not positioned well. For it to position it well, I need to subtract 40 points to its current top safeAreaInsets. The initial 20 points is to cope with the status bar. When I do this it works and it then doesn't work on the other iPhones that don't have a notch. What I'm doing wrong here. Here is my code
func contentFrame(extraTopOffset pOffset: CGFloat = 0) -> CGRect {
var theFrame = self.view.bounds
if let navBar = self.navigationBar {
let topOffset = navBar.frame.origin.y + navBar.frame.size.height
theFrame.size.height -= topOffset
theFrame.origin.y += topOffset
}
if #available(iOS 11.0, *) {
let safeInsets = UIApplication.shared.delegate!.window!?.safeAreaInsets
let topOffset = safeInsets!.top - (20 + pOffset)
theFrame.origin.y += topOffset
theFrame.size.height -= topOffset + safeInsets!.bottom
}
return theFrame
}
Where I show the overlay in another viewController where I override the method
override func showOverlay() {
self.overlayView.frame = self.contentFrame()
self.view.addSubview(self.overlayView)
}

I fixed it by using my implementation. I check if current device has a notch and If it does, I subtract 20 from it. Then add 20 points when I call the method
class var hasNotch: Bool {
if #available(iOS 11.0, *) {
return (self.shared.delegate!.window!!.safeAreaInsets.top > 20)
}
return false
}
func contentFrame(extraTopOffset pOffset: CGFloat = 0) -> CGRect {
var theFrame = self.view.bounds
if let navBar = self.navigationBar {
let topOffset = navBar.frame.origin.y + navBar.frame.size.height
theFrame.size.height -= topOffset
theFrame.origin.y += topOffset
}
if #available(iOS 11.0, *) {
let safeInsets = UIApplication.shared.delegate!.window!?.safeAreaInsets
var extraOffset = safeInsets!.top - 20
if UIApplication.yb_hasNotch {
extraOffset -= pOffset
}
theFrame.origin.y += extraOffset
theFrame.size.height -= extraOffset + safeInsets!.bottom
}
return theFrame
}

You can use an extension like this.
extension UIDevice {
var hasNotch: Bool {
if #available(iOS 11.0, *) {
let bottom = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
return bottom > 0
} else {
return false
}
}
}

Related

UIImageView disappears after rotate and move to edge

I have written a custom class which offers the feature to move and rotates images.
I need to restrict the movement to the boundaries of parent view or Superview.
So, I wrote below code to restrict it.
This works fine before an image is rotated. If I try to rotate the image and then move the image to an edge, Image disappears leaving no log or traces.
Why does it disappear and how do I avoid it?
if(frame.origin.x < 1)
{
frame.origin.x = 1
}
if(frame.origin.y < 1)
{
frame.origin.y = 1
}
if(frame.maxX > superview!.frame.width)
{
frame.origin.x = superview!.frame.width - frame.width - 1
}
if(frame.maxY > superview!.frame.height)
{
frame.origin.y = superview!.frame.height - frame.height - 1
}
If I remove the above code, nothing disappears but image moves out of boundaries. So I feel something wrong in only above lines.
So help me to correctly implement this feature after rotation.
Full Movable Image Class code :
class movableImageView: UIImageView
{
var CenCooVar = CGPoint()
override init(image: UIImage!)
{
super.init(image: image)
self.userInteractionEnabled = true
let moveImage = UIPanGestureRecognizer(target: self, action: #selector(moveImageFnc(_:)))
let rotateImage = UIRotationGestureRecognizer(target: self, action: #selector(rotateImageFnc(_:)))
self.gestureRecognizers = [moveImage,rotateImage]
}
func moveImageFnc(moveImage: UIPanGestureRecognizer)
{
if moveImage.state == UIGestureRecognizerState.Began
{
CenCooVar = self.center
}
if moveImage.state == UIGestureRecognizerState.Changed
{
let moveCooVar = moveImage.translationInView(self.superview!)
self.center = CGPoint(x: CenCooVar.x + moveCooVar.x, y: CenCooVar.y + moveCooVar.y)
if(frame.origin.x < 1)
{
frame.origin.x = 1
}
if(frame.origin.y < 1)
{
frame.origin.y = 1
}
if(frame.maxX > superview!.frame.width)
{
frame.origin.x = superview!.frame.width - frame.width - 1
}
if(frame.maxY > superview!.frame.height)
{
frame.origin.y = superview!.frame.height - frame.height - 1
}
}
if moveImage.state == UIGestureRecognizerState.Ended
{
CenCooVar = self.center
}
}
func rotateImageFnc(rotateImage: UIRotationGestureRecognizer)
{
if rotateImage.state == UIGestureRecognizerState.Changed
{
self.transform = CGAffineTransformRotate(self.transform, rotateImage.rotation)
rotateImage.rotation = 0
}
}
}

iPhone X not respecting set tab bar hidden animated

I've got a view controller being pushed onto a navigation controller which is inside a tab bar controller. The view controller hidesBottomBarWhenPushed and on view will appear it shows a toolbar. No matter what I try, it won't stop animating the toolbar sliding up / down when the view controller is pushed or popped. This only seems to be an issue on the iPhone X. Does anyone know how work around it?
This answer https://stackoverflow.com/a/47225653/1211917 helps me:
class SafeAreaFixTabBar: UITabBar {
var oldSafeAreaInsets = UIEdgeInsets.zero
#available(iOS 11.0, *)
override func safeAreaInsetsDidChange() {
super.safeAreaInsetsDidChange()
if oldSafeAreaInsets != safeAreaInsets {
oldSafeAreaInsets = safeAreaInsets
invalidateIntrinsicContentSize()
superview?.setNeedsLayout()
superview?.layoutSubviews()
}
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
var size = super.sizeThatFits(size)
if #available(iOS 11.0, *) {
let bottomInset = safeAreaInsets.bottom
if bottomInset > 0 && size.height < 50 && (size.height + bottomInset < 90) {
size.height += bottomInset
}
}
return size
}
override var frame: CGRect {
get {
return super.frame
}
set {
var tmp = newValue
if let superview = superview, tmp.maxY !=
superview.frame.height {
tmp.origin.y = superview.frame.height - tmp.height
}
super.frame = tmp
}
}
}

navigationItem.titleView is taking space form the leftBarButtonItems in ios 11

I am setting titleView using this method
func addConstrainstToSearchField(with size: CGSize? = nil) {
var width : CGFloat = 0.0
if let searchField = self.searchfield {
var frame = searchField.frame
if size != nil, let frameWidth = size?.width {
width = frameWidth
} else if let frameWidth = self.navigationController?.navigationBar.frame.size.width {
width = frameWidth
}
frame.size.width = (width - 145.0) * 0.90 // 136 is width of (left and right BarButtonItems) button
searchField.frame = frame
self.navigationItem.titleView = searchField
// DispatchQueue.main.async(execute: {
// self.navigationItem.titleViewWithConstraint = searchField
// })
}
}
and I am calling this method from viewdidload and viewWillTransiion
but it's not showing properly
screenshot In landscape
image 2
thanks in advance

UIScrollView scrolls out of image after image is zoomed in?

I'm trying to implement a image zooming functionality using UIScrollview. where as I kept image as aspect fit.
Image is inside a UIScrollView, and image frame has been given similar to UIScrollView.
Here is my code.
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view.
scroller.minimumZoomScale = 1.0
scroller.maximumZoomScale = 7.0
}
// MARK: - User Defined Methods
#IBAction func doubleTapGestureAction(_ sender: UITapGestureRecognizer)
{
if scroller.zoomScale == 1
{
scroller.zoom(to: zoomForScale(scale: scroller.maximumZoomScale, center: sender.location(in: sender.view)), animated: true)
}
else
{
scroller.setZoomScale(1, animated: true)
}
print(isZoomedIn)
}
func zoomForScale(scale: CGFloat, center: CGPoint) -> CGRect
{
var zoomRect = CGRect.zero
zoomRect.size.height = image.frame.size.height / scale
zoomRect.size.width = image.frame.size.width / scale
let newCenter = image.convert(center, from: scroller)
zoomRect.origin.x = newCenter.x - (zoomRect.size.width / 2.0)
zoomRect.origin.y = newCenter.y - (zoomRect.size.height / 2.0)
return zoomRect
}
func viewForZooming(in scrollView: UIScrollView) -> UIView?
{
return image
}
Here is sample code:
import UIKit
class ViewController: UIViewController,UIScrollViewDelegate {
var imgDemo: UIImageView = {
let img = UIImageView()
img.contentMode = .scaleAspectFill
img.isUserInteractionEnabled = true
return img
}()
var scrollView:UIScrollView = {
let scroll = UIScrollView()
scroll.maximumZoomScale = 4.0
scroll.minimumZoomScale = 0.25
scroll.clipsToBounds = true
return scroll
}()
override func viewDidLoad() {
super.viewDidLoad()
imgDemo.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height)
imgDemo.image = UIImage(named: "5.jpg")
scrollView.delegate = self
scrollView.frame = imgDemo.frame
scrollView.addSubview(imgDemo)
view.addSubview(scrollView)
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imgDemo
}
}
Take a look at these methods. May be it will help. I have scroll view stretched to controller's view size. customizeScrollView() will calculate min and max scale options. centerImageView() will put UIImageView in the center of your UIScrollView
Call the customizeScrollView function in viewDidload.
fileprivate func customizeScrollView() {
guard let image = imageView?.image else { return }
var minZoom = fmin(self.view.frame.width / image.size.width, self.view.frame.height / image.size.height)
minZoom = fmin(1.0, minZoom)
scrollView?.contentSize = image.size
scrollView?.minimumZoomScale = minZoom
scrollView?.addSubview(self.imageView!)
scrollView?.setZoomScale(minZoom, animated: false)
centerImageView()
}
fileprivate func centerImageView() {
guard let imageView = imageView else { return }
guard let scrollView = scrollView else { return }
let boundsSize = scrollView.bounds.size
var frameToCenter = imageView.frame
// Center horizontally
if frameToCenter.size.width < boundsSize.width {
frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2
} else {
frameToCenter.origin.x = 0
}
// Center vertically
if frameToCenter.size.height < boundsSize.height {
frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2
} else {
frameToCenter.origin.y = 0
}
imageView.frame = frameToCenter
}
public func scrollViewDidZoom(scrollView: UIScrollView) {
print(imageView.frame)
centerImageView()
}

Why collectionView cell centering works only in one direction?

I'm trying to centerelize my cells on horizontal scroll. I've written one method, but it works only when I scroll to right, on scroll on left it just scrolls, without stopping on the cell's center.
Can anyone help me to define this bug, please?
class CenterCellCollectionViewFlowLayout: UICollectionViewFlowLayout {
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
if let cv = self.collectionView {
let cvBounds = cv.bounds
let halfWidth = cvBounds.size.width * 0.5;
let proposedContentOffsetCenterX = proposedContentOffset.x + halfWidth;
if let attributesForVisibleCells = self.layoutAttributesForElementsInRect(cvBounds) {
var candidateAttributes: UICollectionViewLayoutAttributes?
for attributes in attributesForVisibleCells {
// == Skip comparison with non-cell items (headers and footers) == //
if attributes.representedElementCategory != UICollectionElementCategory.Cell {
continue
}
if (attributes.center.x == 0) || (attributes.center.x > (cv.contentOffset.x + halfWidth) && velocity.x < 0) {
continue
}
// == First time in the loop == //
guard let candAttrs = candidateAttributes else {
candidateAttributes = attributes
continue
}
let a = attributes.center.x - proposedContentOffsetCenterX
let b = candAttrs.center.x - proposedContentOffsetCenterX
if fabsf(Float(a)) < fabsf(Float(b)) {
candidateAttributes = attributes;
}
}
if(proposedContentOffset.x == -(cv.contentInset.left)) {
return proposedContentOffset
}
return CGPoint(x: floor(candidateAttributes!.center.x - halfWidth), y: proposedContentOffset.y)
}
} else {
print("else")
}
// fallback
return super.targetContentOffsetForProposedContentOffset(proposedContentOffset)
}
}
And in my UIViewController:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
var insets = self.collectionView.contentInset
let value = ((self.view.frame.size.width - ((CGRectGetWidth(collectionView.frame) - 35))) * 0.5)
insets.left = value
insets.right = value
self.collectionView.contentInset = insets
self.collectionView.decelerationRate = UIScrollViewDecelerationRateNormal
}
If you have any question - please ask me
I actually just did a slightly different implementation for another thread, but adjusted it to work for this questions. Try out the solution below :)
/**
* Custom FlowLayout
* Tracks the currently visible index and updates the proposed content offset
*/
class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout {
// Tracks the currently visible index
private var visibleIndex : Int = 0
// The width offset threshold percentage from 0 - 1
let thresholdOffsetPrecentage : CGFloat = 0.5
// This is the flick velocity threshold
let velocityThreshold : CGFloat = 0.4
override init() {
super.init()
self.minimumInteritemSpacing = 0.0
self.minimumLineSpacing = 0.0
self.scrollDirection = .Horizontal
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
let leftThreshold = CGFloat(collectionView!.bounds.size.width) * ((CGFloat(visibleIndex) - 0.5))
let rightThreshold = CGFloat(collectionView!.bounds.size.width) * ((CGFloat(visibleIndex) + 0.5))
let currentHorizontalOffset = collectionView!.contentOffset.x
// If you either traverse far enough in either direction,
// or flicked the scrollview over the horizontal velocity in either direction,
// adjust the visible index accordingly
if currentHorizontalOffset < leftThreshold || velocity.x < -velocityThreshold {
visibleIndex = max(0 , (visibleIndex - 1))
} else if currentHorizontalOffset > rightThreshold || velocity.x > velocityThreshold {
visibleIndex += 1
}
var _proposedContentOffset = proposedContentOffset
_proposedContentOffset.x = CGFloat(collectionView!.bounds.width) * CGFloat(visibleIndex)
return _proposedContentOffset
}
}

Resources