how to move view when keyboard appears on small devices only - swift

I am working on iPhone application and I have multiple UITextFields for input.
the problem with small devices only like iPhone 5 , 6. when The keyboard appears , the bottom textFields hide.
it is working fine with big iPhone screen like XR , XS Max
how can I add condition that check if the bottom textfields Hidden or not ?
guard let keyboardReact = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {
return
}
let screen = view.frame.size.height - keyboardReact.height
let safeAreHeight = self.view.frame.height - self.topLayoutGuide.length - self.bottomLayoutGuide.length
if safeAreHeight + keyboardReact.height > view.frame.size.height {
if currentTappedTextField == phoneTextField || currentTappedTextField == employeeEmailTextField || currentTappedTextField == relationTextField {
if notification.name == UIResponder.keyboardWillShowNotification || notification.name == UIResponder.keyboardWillChangeFrameNotification{
view.frame.origin.y = -(keyboardReact.height)
} else {
view.frame.origin.y = 0
}
}
}
This is work with all screen sizes I want it work only when the keyboard hide the textFields

Now you can calculate your keyboard height and move your view accodingly
func liftViewUp(notification: NSNotification){
if let keyboardSize = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? CGRect {
// manage your view accordingly here
if currentTappedTextField == phoneTextField || currentTappedTextField == employeeEmailTextField || currentTappedTextField == relationTextField {
if notification.name == UIResponder.keyboardWillShowNotification || notification.name == UIResponder.keyboardWillChangeFrameNotification{
let textFieldPosition = currentTappedTextField.frame.origin.y + currentTappedTextField.frame.size.height
// check if textfield will hide behind keyboard
if textFieldPosition > (view.frame.size.height - keyboardReact.height){
view.frame.origin.y = -(keyboardReact.height)
}else {
view.frame.origin.y = 0
}
} else {
view.frame.origin.y = 0
}
}
}
}
or You can try this third party library IQKeyboardManager
You may got answer here

Related

How can i make a functionality that uses if statements to switch images based on a random result

I have this simple app that is a dice app that has two dice. I want to show a label "JACKPOT!" at alpha(transparency) 1.0 when the user has both dices land with the same number.
Example:
User rolls DiceOne && DiceOne label "jackpot!" changes alpha to 1.0
I tried using an if statement with an array of 5 images of each possible dice number ie: 1,2,3,4,5,6
The code that i wrote worked one day then the next day it no longer works.
let dicearray = [#imageLiteral(resourceName: "DiceOne"), #imageLiteral(resourceName: "DiceTwo"), #imageLiteral(resourceName: "DiceThree"), #imageLiteral(resourceName: "DiceFour"), #imageLiteral(resourceName: "DiceFive"), #imageLiteral(resourceName: "DiceSix")]`
func jackpotShows() {
if diceImageView1.image == dicearray[0] && diceImageView2.image == dicearray[0] {
jackpotLabel.alpha = 1.0
}
}
Change your if statement from
//This is wrong
if diceImageView1.image == dicearray[0] && diceImageView2.image == dicearray[0] {
jackpotLabel.alpha = 1.0
}
to
//This is right
if diceImageView1.image == diceImageView2.image {
jackpotLabel.alpha = 1.0
}
Or, alternatively, you could do a for loop:
//This is right
for image in diceArray {
if diceImageView1.image == image && diceImageView2.image == image {
jackpotLabel.alpha = 1.0
}
}

Swift: Finding views in a superview (without a for loop)

I'm using the following code to find a certain subview inside my view. It works fine, and the for loop runs only once because it only finds one subview that satisfies the where clause.
for view in boardView.subviews where (view as? PlayingCardView)?.position.location == source.
&& (view as! PlayingCardView).position.column == source.column
&& (view as! PlayingCardView).position.row >= source.row
However, it seems weird that I'm running a loop here when I'm not actually doing any...loop-y things (know what I mean)?
Is there some other way to "search" like this? Like a way to use subviews.index(of:) and use the conditions from the where clause, or something like that?
Also, I know that I could do the same code like this:
for view in boardView.subviews {
if let cardView = view as? PlayingCardView {
if cardView.position.location == source.location
&& (view as! PlayingCardView).position.column == source.column
&& (view as! PlayingCardView).position.row >= source.row {
// Do stuff
}
}
}
Is one of these ways computationally faster?
I believe you're looking for the filter method.
if let card = (boardView.subviews as? [PlayingCardView])?.filter({
return $0.position.location == source.location
&& $0.position.column == source.column
&& $0.position.row >= source.row
}).first {
// Do stuff
print(card)
}
Or if you want to find the first card that satisfies your arguments, you can use the first method.
if let card = (boardView.subviews as? [PlayingCardView])?.first(where: {
return $0.position.location == source.location
&& $0.position.column == source.column
&& $0.position.row >= source.row
}) {
// Do stuff
print(card)
}
first(where: ) will give you the first element in the array that satisfies a condition (assuming that you only want to do it on one element since it's not "loopy"):
let view = boardView.subviews.first {
guard let cardView = $0 as? PlayingCardView else { return false }
return cardView.position.location == source.location
&& cardView.position.column == source.column
&& cardView.position.row >= source.row
}
// do stuffs to `view`
It will stop as soon as a match is found so that's about as efficient as you can get. In reality though, it hardly matters as you tend to have only a small number of subviews. Otherwise the GPU will give out first from having to render all of them.

UI Test - System Alert in Landscape mode

I am working on UI Test for several System Alerts in a row (ie. a video app to get permission for Camera, Microphone and Photos). With a sample project, it seems the new method addUIInterruptionMonitorWithDescription is not working for Landscape mode.
I come across this post Swift UI Test - User Notifications System Alert, but the case is different for me.
My code looks like this:
let desc = "\u{201c}Alert\u{201d} Would Like to Access the Camera"
let app = XCUIApplication()
addUIInterruptionMonitorWithDescription(desc) { (alert) -> Bool in
let okButton = alert.buttons["OK"]
print(okButton.frame)
okButton.tap()
return true
}
app.buttons["Alert"].tap()
It works for Portrait, not Landscape. The case can be reproduced by Simulator and Device.
Moreover the okButton.frame I got in Portrait is
CGRect
▿ origin : CGPoint
- x : 207.0
- y : 387.666666666667
▿ size : CGSize
- width : 135.0
- height : 44.0
but the frame in Landscape shows like this
CGRect
▿ origin : CGPoint
- x : 143.333333333333
- y : 368.0
▿ size : CGSize
- width : 44.0
- height : 135.0
The test failure error I got is this one
test failure: -[AlertUITests testExample()] failed: UI Testing Failure - Failed to scroll to visible (by AX action) Button 0x14df73840: traits: 8589934593, {{277.0, 345.0}, {46.0, 30.0}}, label: 'Button', error: Error -25204 performing AXAction 2003
Any idea?
EDIT 1
Submitted to Radar rdar://23931990
It's a framework bug.
Try use something like this:
Helpers:
var device: XCUIDevice {return XCUIDevice.shared()}
extension XCUIDevice {
func type() -> String {
if XCUIApplication().windows.element(boundBy: 0).horizontalSizeClass == .compact || XCUIApplication().windows.element(boundBy: 0).verticalSizeClass == .compact {
return "iPhone"
} else {
return "iPad"
}
}
}
Method:
func handleOrientation(_ orientationAltered: Bool) -> Bool {
guard device.type() == "iPad" else {return false}
if !orientationAltered && device.orientation == .landscapeLeft {
device.orientation = .portrait
return true
} else if orientationAltered && (device.orientation == .landscapeLeft || device.orientation == .landscapeRight) {
device.orientation = .landscapeLeft
return false
} else {return false}
}

Use UIPageViewController together with UIPanGestureRecognizer

My application is a page based application using UIPageViewController. Now I would like to able to move subviews vertically on my pages. After implementing a UIPanGestureRecognizer I was able to move the subviews but now my horizontal swiping to move through the pages does not work...
How can I combine those two gestures?
This is my panGestureRecognizer:
ContentViewController.swift
var panSwipe = UIPanGestureRecognizer(target: self, action: Selector("handlePanSwipe:"))
view.addGestureRecognizer(panSwipe)
...
func handlePanSwipe(sender:UIPanGestureRecognizer) {
var translation = sender.translationInView(self.view)
if sender.state == UIGestureRecognizerState.Began {
startLocation = sender.locationInView(self.view)
} else if (sender.state == UIGestureRecognizerState.Changed) {
self.settingsView.frame.origin.y = self.newSettingsFrameOrigin!.y + translation.y
} else if (sender.state == UIGestureRecognizerState.Ended) {
self.newSettingsFrameOrigin = self.settingsView.frame.origin
}
I want to be able to move the dark green subview up and down:
Thanks in advance!

Determine if UIView is visible to the user?

is it possible to determine whether my UIView is visible to the user or not?
My View is added as subview several times into a Tab Bar Controller.
Each instance of this view has a NSTimer that updates the view.
However I don't want to update a view which is not visible to the user.
Is this possible?
Thanks
For anyone else that ends up here:
To determine if a UIView is onscreen somewhere, rather than checking superview != nil, it is better to check if window != nil. In the former case, it is possible that the view has a superview but that the superview is not on screen:
if (view.window != nil) {
// do stuff
}
Of course you should also check if it is hidden or if it has an alpha > 0.
Regarding not wanting your NSTimer running while the view is not visible, you should hide these views manually if possible and have the timer stop when the view is hidden. However, I'm not at all sure of what you're doing.
You can check if:
it is hidden, by checking view.hidden
it is in the view hierarchy, by checking view.superview != nil
you can check the bounds of a view to see if it is on screen
The only other thing I can think of is if your view is buried behind others and can't be seen for that reason. You may have to go through all the views that come after to see if they obscure your view.
This will determine if a view's frame is within the bounds of all of its superviews (up to the root view). One practical use case is determining if a child view is (at least partially) visible within a scrollview.
Swift 5.x:
func isVisible(view: UIView) -> Bool {
func isVisible(view: UIView, inView: UIView?) -> Bool {
guard let inView = inView else { return true }
let viewFrame = inView.convert(view.bounds, from: view)
if viewFrame.intersects(inView.bounds) {
return isVisible(view: view, inView: inView.superview)
}
return false
}
return isVisible(view: view, inView: view.superview)
}
Older swift versions
func isVisible(view: UIView) -> Bool {
func isVisible(view: UIView, inView: UIView?) -> Bool {
guard let inView = inView else { return true }
let viewFrame = inView.convertRect(view.bounds, fromView: view)
if CGRectIntersectsRect(viewFrame, inView.bounds) {
return isVisible(view, inView: inView.superview)
}
return false
}
return isVisible(view, inView: view.superview)
}
Potential improvements:
Respect alpha and hidden.
Respect clipsToBounds, as a view may exceed the bounds of its superview if false.
The solution that worked for me was to first check if the view has a window, then to iterate over superviews and check if:
the view is not hidden.
the view is within its superviews bounds.
Seems to work well so far.
Swift 3.0
public func isVisible(view: UIView) -> Bool {
if view.window == nil {
return false
}
var currentView: UIView = view
while let superview = currentView.superview {
if (superview.bounds).intersects(currentView.frame) == false {
return false;
}
if currentView.isHidden {
return false
}
currentView = superview
}
return true
}
I benchmarked both #Audrey M. and #John Gibb their solutions.
And #Audrey M. his way performed better (times 10).
So I used that one to make it observable.
I made a RxSwift Observable, to get notified when the UIView became visible.
This could be useful if you want to trigger a banner 'view' event
import Foundation
import UIKit
import RxSwift
extension UIView {
var isVisibleToUser: Bool {
if isHidden || alpha == 0 || superview == nil {
return false
}
guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else {
return false
}
let viewFrame = convert(bounds, to: rootViewController.view)
let topSafeArea: CGFloat
let bottomSafeArea: CGFloat
if #available(iOS 11.0, *) {
topSafeArea = rootViewController.view.safeAreaInsets.top
bottomSafeArea = rootViewController.view.safeAreaInsets.bottom
} else {
topSafeArea = rootViewController.topLayoutGuide.length
bottomSafeArea = rootViewController.bottomLayoutGuide.length
}
return viewFrame.minX >= 0 &&
viewFrame.maxX <= rootViewController.view.bounds.width &&
viewFrame.minY >= topSafeArea &&
viewFrame.maxY <= rootViewController.view.bounds.height - bottomSafeArea
}
}
extension Reactive where Base: UIView {
var isVisibleToUser: Observable<Bool> {
// Every second this will check `isVisibleToUser`
return Observable<Int>.interval(.milliseconds(1000),
scheduler: MainScheduler.instance)
.map { [base] _ in
return base.isVisibleToUser
}.distinctUntilChanged()
}
}
Use it as like this:
import RxSwift
import UIKit
import Foundation
private let disposeBag = DisposeBag()
private func _checkBannerVisibility() {
bannerView.rx.isVisibleToUser
.filter { $0 }
.take(1) // Only trigger it once
.subscribe(onNext: { [weak self] _ in
// ... Do something
}).disposed(by: disposeBag)
}
Tested solution.
func isVisible(_ view: UIView) -> Bool {
if view.isHidden || view.superview == nil {
return false
}
if let rootViewController = UIApplication.shared.keyWindow?.rootViewController,
let rootView = rootViewController.view {
let viewFrame = view.convert(view.bounds, to: rootView)
let topSafeArea: CGFloat
let bottomSafeArea: CGFloat
if #available(iOS 11.0, *) {
topSafeArea = rootView.safeAreaInsets.top
bottomSafeArea = rootView.safeAreaInsets.bottom
} else {
topSafeArea = rootViewController.topLayoutGuide.length
bottomSafeArea = rootViewController.bottomLayoutGuide.length
}
return viewFrame.minX >= 0 &&
viewFrame.maxX <= rootView.bounds.width &&
viewFrame.minY >= topSafeArea &&
viewFrame.maxY <= rootView.bounds.height - bottomSafeArea
}
return false
}
I you truly want to know if a view is visible to the user you would have to take into account the following:
Is the view's window not nil and equal to the top most window
Is the view, and all of its superviews alpha >= 0.01 (threshold value also used by UIKit to determine whether it should handle touches) and not hidden
Is the z-index (stacking value) of the view higher than other views in the same hierarchy.
Even if the z-index is lower, it can be visible if other views on top have a transparent background color, alpha 0 or are hidden.
Especially the transparent background color of views in front may pose a problem to check programmatically. The only way to be truly sure is to make a programmatic snapshot of the view to check and diff it within its frame with the snapshot of the entire screen. This won't work however for views that are not distinctive enough (e.g. fully white).
For inspiration see the method isViewVisible in the iOS Calabash-server project
The simplest Swift 5 solution I could come up with that worked in my situation (I was looking for a button embedded in my tableViewFooter).
John Gibbs solution also worked but in my cause I did not need all the recursion.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let viewFrame = scrollView.convert(targetView.bounds, from: targetView)
if viewFrame.intersects(scrollView.bounds) {
// targetView is visible
}
else {
// targetView is not visible
}
}
In viewWillAppear set a value "isVisible" to true, in viewWillDisappear set it to false. Best way to know for a UITabBarController subviews, also works for navigation controllers.
Another useful method is didMoveToWindow()
Example: When you push view controller, views of your previous view controller will call this method. Checking self.window != nil inside of didMoveToWindow() helps to know whether your view is appearing or disappearing from the screen.
This can help you figure out if your UIView is the top-most view. Can be helpful:
let visibleBool = view.superview?.subviews.last?.isEqual(view)
//have to check first whether it's nil (bc it's an optional)
//as well as the true/false
if let visibleBool = visibleBool where visibleBool { value
//can be seen on top
} else {
//maybe can be seen but not the topmost view
}
try this:
func isDisplayedInScreen() -> Bool
{
if (self == nil) {
return false
}
let screenRect = UIScreen.main.bounds
//
let rect = self.convert(self.frame, from: nil)
if (rect.isEmpty || rect.isNull) {
return false
}
// 若view 隐藏
if (self.isHidden) {
return false
}
//
if (self.superview == nil) {
return false
}
//
if (rect.size.equalTo(CGSize.zero)) {
return false
}
//
let intersectionRect = rect.intersection(screenRect)
if (intersectionRect.isEmpty || intersectionRect.isNull) {
return false
}
return true
}
In case you are using hidden property of view then :
view.hidden (objective C) or view.isHidden(swift) is read/write property. So you can easily read or write
For swift 3.0
if(view.isHidden){
print("Hidden")
}else{
print("visible")
}