Using UserDefaults in viewWillAppear - swift

I'm setting up the Settings page (in SettingsView class) where users can set «On» / «Off» for the background parallax effect. The selection is saved in UserDefaults().string(forKey: "parallaxStatus"). In the viewWillAppear of ViewController class, I checked the parallaxStatus. If the status of the parallax effect is «On», then this effect is displayed. If the status is «Off», then nothing should happen.
The problem appeared when parallaxStatus changed from «On» to «Off» In this case, the parallax effect still displayed before I reload the View. But if parallaxStatus changed from «Off» to «On», the function works well without reloading the View.
Bellow is the code of viewWillAppear function. Thanks for any help or hint.
override func viewWillAppear(_ animated: Bool) {
let parallaxStatus = UserDefaults().string(forKey: "parallaxStatus")
if parallaxStatus == "On" {
let min = CGFloat(-40)
let max = CGFloat(40)
let xMotion = UIInterpolatingMotionEffect(keyPath: "layer.transform.translation.x", type: .tiltAlongHorizontalAxis)
xMotion.minimumRelativeValue = min
xMotion.maximumRelativeValue = max
let yMotion = UIInterpolatingMotionEffect(keyPath: "layer.transform.translation.y", type: .tiltAlongVerticalAxis)
yMotion.minimumRelativeValue = min
yMotion.maximumRelativeValue = max
let motionEffectGroup = UIMotionEffectGroup()
motionEffectGroup.motionEffects = [xMotion,yMotion]
bgImage.addMotionEffect(motionEffectGroup) } else { }
}

1- You should use a bool value in userDefaults
UserDefaults.standard.bool(forKey: "parallaxStatusOn") // default is false
2- viewWillAppear is called when you dimiss a presented / poped vc so in your case , you use a settingsView not vc verify it's being called by other KVO or any event driven notifier
3- if the state is on and changed to off , verify you remove the motion effects with if the vc is still appeared ( not deallocated btw do it in else of check )
bgImage.motionEffects.forEach { bgImage.removeMotionEffect($0) }

Related

Add alpha to parentVC.view

I am trying to add alpha to the background view when tapped on a button. So far achieved adding blur but alpha not so much.
How can I add alpha to the background so that when the bottom sheet appears background will be darker and disabled.
let maxDimmedAlpha: CGFloat = 0.2
lazy var dimmedView: UIView = {
let view = UIView()
view.backgroundColor = .black
view.alpha = maxDimmedAlpha
return view
}()
#objc func shareBtnClick() {
dimmedView.frame = self.parentVC.view.bounds
dimmedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.parentVC.view.addSubview(dimmedView)
if self.parentVC.navigationController != nil {
if self.parentVC.navigationController?.viewControllers.count == 1 {
showBottomSheet()
} else {
NotificationCenter.default.post(name: NSNotification.Name("ShowBottomSheet"), object: nil, userInfo: ["itemId": modalSheet(), "delegate": self])
}
} else {
showBottomSheet()
}
}
func showBottomSheet() {
let modalSheet = MainBottomSheet()
modalSheet.data = self.modalSheet()
modalSheet.delegate = self
modalSheet.modalPresentationStyle = .overCurrentContext
self.parentVC.present(modalSheet, animated: true)
}
I was able to produce the dimmed effect using this code in XCode, I'm not sure why it won't work in your project but there is an easy way to debug this.
I suggest using Debug View Hierarchy, one of XCode's best tools in my opinion. This allows you to separate every single layer of the user interface. This way, you can see if your dimmedView is actually being added to the parent view and that its frame is matching the parent view's bounds.
Keep in mind if your background is dark, you won't see this dimmedView because its backgroundColor is set to UIColor.black.
Debug View Hierarchy button

How to connect to an external display using iOS13 Swift or SwiftUI

Maybe a bit of a newbie here, so forgive me if the an easy fix, but I have used the below code pre ios13 to display a image on an external display, but now Ive come across a warning.
The warning is 'Setter for 'screen' was deprecated in iOS 13.0'
MY CODE:
#objc func setupScreen(){
if UIScreen.screens.count > 1{
//find the second screen
let secondScreen = UIScreen.screens[1]
secondScreen.overscanCompensation = .none
//set up a window for the screen using the screens pixel dimensions
secondWindow = UIWindow(frame: secondScreen.bounds)
//windows require a root view controller
let viewcontroller = UIViewController()
secondWindow?.rootViewController = viewcontroller
//tell the window which screen to use
secondWindow?.screen = secondScreen
//set the dimensions for the view for the external screen so it fills the screen
secondScreenView = UIView(frame: secondWindow!.frame)
//add the view to the second screens window
secondWindow?.addSubview(secondScreenView!)
//unhide the window
secondWindow?.isHidden = false
//customised the view
let img = UIImage(named: "chart.png")
// view.layer.contents = img?.cgImage
secondScreenView?.layer.contents = img?.cgImage
// secondScreenView!.backgroundColor = UIColor(patternImage: UIImage(named: "chart.png")!)
secondScreenView!.addSubview(externalLabel)
}
}
#objc func screenDisconnected(){
secondWindow = nil
secondScreenView = nil
}
func registerForScreenNotifications(){
NotificationCenter.default.addObserver(self, selector: #selector(testPatterns.setupScreen), name: UIScreen.didConnectNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(testPatterns.screenDisconnected), name: UIScreen.didDisconnectNotification, object: nil)
}
}
I tried looking in the Apple documentation but I can't find anything?
Any help would be great.
P.S it does still work but would like to get rid of the warning.
cheers!

No-show menuController and can't figure out how to make the View return True to calling .becomeFirstResponder

I am having a no-show menuController and I have checked all of the suggestions in previous questions. It turns out the imageView I have implemented a UILongPressGestureRecognizer on, to show the menu, is returning False on calling .becomeFirstResponder just before setting up the menu controller.
I am coding in swift 4 and can't figure out how to make the imageView return True to calling .becomeFirstResponder. Help!
/*********************************************************/
override func viewDidLoad() {
super.viewDidLoad()
// long tap to show menu that enables deletion of the image.
imageView_1.isUserInteractionEnabled = true
let longPressRecogniser = UILongPressGestureRecognizer(target: self, action: #selector(longPressOnImage(_:)))
//longPressRecogniser.numberOfTapsRequired = 1
//longPressRecogniser.numberOfTouchesRequired = 1
longPressRecogniser.minimumPressDuration = 0.5
imageView_1.addGestureRecognizer(longPressRecogniser)
imageView_1.image = placeHolderImage_1
imageView_2.image = placeHolderImage_2
}
/*********************************************************/
#IBAction func longPressOnImage(_ gestureRecognizer: UILongPressGestureRecognizer) {
print(#function)
if gestureRecognizer.state == .began {
//print("gestureRecognizer.state == .began")
self.tappedView = gestureRecognizer.view!
if tappedView.canResignFirstResponder {
print("can resign first responder")
}
if tappedView.becomeFirstResponder() {
print("returned TRUE to becomeFirstResponder")
} else {
print("returned FALSE to becomeFirstResponder")
}
// Configure the shared menu controller
let menuController = UIMenuController.shared
// Configure the menu item to display
// Create a "delete" menu item
let deleteImage = UIMenuItem(title: "Delete", action: #selector(deleteImage_1))
menuController.menuItems = [deleteImage]
// Set the location of the menu in the view.
let location = gestureRecognizer.location(in: tappedView)
print("location = ", location)
let menuLocation = CGRect(x: location.x, y: location.y, width: 2, height: 2)
menuController.setTargetRect(menuLocation, in: tappedView)
//update the menu settings to force it to display my custom items
menuController.update()
// Show the menu.
menuController.setMenuVisible(true, animated: true)
print("menu should be visible now")
}
}
/*********************************************************/
#objc func deleteImage_1() {
print(#function)
}
My caveman debugging print statements output:
longPressOnImage
can resign first responder
returned FALSE to becomeFirstResponder
location = (207.0, 82.0)
menu should be visible now
Create a custom imageView class and override "canBecomeFirstResponder" property like this:
class ResponsiveImage : UIImageView{
override var canBecomeFirstResponder: Bool{
return true
}
}
Use this ResponsiveImage type and your code will work :)
Thank you to adri. Your answer is the solution to my problem.
I had read in other posts to similar questions about overriding var canBecomeFirstResponder but either overlooked it or it wasn't made explicit that a custom UIImageView class needs to be created.
Just to make it clear to newbies like me, the class of the imageView in storyBoard and its #IBOutlet in its viewController must typed as ResponsiveImage. If only one of these is changed a type casting error is reported.
Many thanks for ending my hours of frustration! :-)

App changes to different controller view when changing orientation

I'm developing an app using Xcode 9.2 and swift 4 and I needed to allow just one view in my app to change to landscape, so I added the code below to my AppDelegate
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if globalVariables.gIsDosageView == "Y" {
if UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight {
return UIInterfaceOrientationMask.all;
} else {
return UIInterfaceOrientationMask.portrait;
}
} else {
return UIInterfaceOrientationMask.portrait;
}
}
The global variable is set to "N" on every other controller and so only DosageView has it set to "Y". In the DosageView controller I added this method to help with the transition.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
hideViews()
if size.width < 400 {
DispatchQueue.main.async() {
self.userImg.isHidden = false
self.burgerImg.isHidden = false
self.deviceImg.isHidden = false
self.portraitView.isHidden = false
self.landscapeView.isHidden = true
}
}
else {
DispatchQueue.main.async() {
self.userImg.isHidden = true
self.burgerImg.isHidden = true
self.deviceImg.isHidden = true
self.portraitView.isHidden = true
self.landscapeView.isHidden = false
self.loadLandscapeView()
}
}
}
I've set up a LandscapeView and a PortraitView and the method above helps to toggle between them and it all works fine. If I move to the next view or return to the main screen and then return to the DosageView and then change the orientation to Landscape it works but if I go to the next view and then from there to the Connect view and connect (via BLE, NFC or QR) it then returns to the DosageView. When I change the orientation to Landscape it changes back to the Connect view. There is no direct link between these two views as you have to go through the Instruction view to get to Connect View for the DosageView, so how can this happen?
When I run it under debug and put a breakpoint in the ViewDidLoad on Connect View, it runs every time the DosageView changes to Landscape. If anyone can tell me what is going on I would be grateful as this is driving me crazy.
It was actually a colleague who sorted this for me and as he correctly pointed out it was a Lottie run call causing the problem. For anyone using Lottie and experiencing this problem, simply check your animation code. I had originally plagiarised code from a Splash screen as below
splashAnimation!.loopAnimation = false
splashAnimation!.play(fromProgress: 0,
toProgress: 1.0,
withCompletion: { (finished) in
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "Menu")
self.present(controller, animated: true, completion: nil)
})
This code works fine as it moves on to the main screen on completion. However, the screen that was causing the problems moves between two different screens and to fix the problem, my colleague simply removed the withCompletion part as below
splashAnimation!.loopAnimation = true
splashAnimation!.play(fromProgress: 0,
toProgress: 1.0)

How to get and change a variable in my view controller from SKScene

Ok so here is the code
class GameViewController: UIViewController, SceneTransitionDelegate,
GKGameCenterControllerDelegate, ADBannerViewDelegate {
var coolbool:Bool = false
...abunch of unimportant stuff functions and stuff
}
And here is what I am trying to do from my SKScene
func thing1()
{
let controller = GameViewController()
controller.coolbool = true
println(controller.coolbool) // Will say that it is true
sceneDelegate.transitionToScene(Menu.self) //Menu.self is the skscene that
we used to be in and will be in
}
func thing2()
{
println(controller.coolbool) // Will say that it is false
if (controller.coolbool == true)
{
//Put rainbows over every sprite and change generator settings
}
}
So basically what happens is that "coolbool" is initialized as being false. Until thing1() is called causing the variable "coolbool " to change. And i confirm its change immediately after, before the transition. However after the transition (to the same scene (I'm trying to make it look different if the bool is true)) if you ask what the value is, it says its false.... even though i just set it to true.
Anyway I assume I am doing something wrong, is their a better way to do this??? Incase you want it here is the transition function
func transitionToScene(sceneClass:Scene.Type) {
playing = false
var sizeRect = UIScreen.mainScreen().applicationFrame
var width = sizeRect.size.width * UIScreen.mainScreen().scale
var height = sizeRect.size.height * UIScreen.mainScreen().scale
let skView = self.view as! SKView
let scene = sceneClass(size: skView.bounds.size)
scene.size = CGSizeMake(width, height)
rwidth = width
rheight = height
swidth = width
sheight = height
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
scene.sceneDelegate = self
skView.presentScene(scene)
}
You need to get the current instance of the GameViewController, so this statement:
let controller = GameViewController()
isn't going to work because it declares a new instance of a controller, which is not related to the SKView you are currently using. Instead, we need to obtain the current, presenting view controller.
Obtaining Instance of the View Controller:
The closest method that can help us here is in the UIResponder class, and it is nextResponder:. Its general function is to find and return the current next responder, which seems vague and not of much use to us, until Apple mentions that:
Subclasses must override this method to set the next responder. UIView implements this method by returning the UIViewController object that manages it (if it has one) or its superview (if it doesn’t)
This means that if we are in a scene, we can find the view controller by using the scene's self.view which is the presenting SKView object (inherits from UIView). All we have to do is find the View Controller in our responder chain.
Swift:
var currentViewController: GameViewController?
var upstreamResponder: UIResponder? = self.view
var found = false
while (found != true){
upstreamResponder = upstreamResponder!.nextResponder()
if let viewController = upstreamResponder as? GameViewController {
currentViewController = viewController
found = true
}
if upstreamResponder == nil {
//could not find VC, PANIC!
break
}
}
Objective-C: (will probably have to #include "GameViewController.h")
GameViewController *currentViewController;
UIResponder *upstreamResponder = self.view;
BOOL found = false;
while (found != true) {
upstreamResponder = [upstreamResponder nextResponder];
if ([upstreamResponder isKindOfClass:[GameViewController class]]) {
currentViewController = (GameViewController *)upstreamResponder;
}
if (upstreamResponder == nil) {
//omg where did VC go?!
break;
}
}
Setting properties on your View Controller
Now that you have your current view controller, it is very simple to set any desired property. So if you wanted to change coolbool, all you have to do is state:
currentViewController.coolbool = true
Hope this helps! -Max