Swift XIB set label text - swift

I am attempting to create an 'alert banner' which displays a pin in a label within the alert banner.
I am calling the alert banner from 'ViewController.swift' with:
AlertBanner().Show(self, pin: 2520)
Fatal error: Unexpectedly found nil while implicitly unwrapping an
Optional value
The error is telling me that the AlertBannerText label is nil. I suspect it has not been created yet, but how would I go about waiting for the label to exist before populating it?
Any the code in the AlertBanner class:
class AlertBanner : UIView {
#IBOutlet var AlertBannerText: UILabel!
static weak var delegate: AlertBannerDelegate!
func Show(_ vc: ViewController, pin: Int){
var onlineBanner: UIView?
var topPadding: CGFloat?
var menuView: UIView?
AlertBannerText.text = "\(pin)"
let window = UIApplication.shared.keyWindow!
if let menu = vc.view.viewWithTag(3) { menuView = menu}
onlineBanner = UINib(nibName: "AlertBanner", bundle: nil).instantiate(withOwner: nil, options: nil).first as? AlertBanner
if #available(iOS 11.0, *) { topPadding = window.safeAreaInsets.top }
onlineBanner?.frame.origin.y = 0 - (window.frame.size.width / 7.5 + topPadding!)
onlineBanner!.frame.size.width = window.frame.size.width
onlineBanner!.frame.size.height = window.frame.size.width / 5
onlineBanner!.tag = 2
vc.view.insertSubview(onlineBanner!, belowSubview: menuView!)
if let banner = vc.view.viewWithTag(2) {
UIView.animate(withDuration: 0.5, delay: 0.0, options: [], animations: {
banner.frame.origin.y = window.frame.size.width / 7.5 + topPadding!
}, completion: nil)
}
}

Doing this
AlertBanner().//////
creates an instance without the layout , hence all outlets are nil , you need to create an instance with
class func getInstance() -> AlertBanner {
let onlineBanner = UINib(nibName: "AlertBanner", bundle: nil).instantiate(withOwner: nil, options: nil).first as! AlertBanner
return onlineBanner
}
and then
AlertBanner.getInstance().//////

Related

How can you change value this progress bar while another tab is selected?

How can I change the progress of a progress bar while another tab of the TabBarController is selected?
Code:
self.RAMBar.progress = Float(response.out)/100
Error:
2022-04-14 09:51:33.027911+0200 ServiceTOOLS Control[4195:217038] ServiceTOOLS_Control/ViewController.swift:299: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
Additional info:
I've only got a ViewController.swift that handles all tabs, this is the erroring code:
func parseRAM(){
guard let url = URL(string: "http://\(hostname):\(port)/STOInfo/Stats/RAM")else{
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: AnyHashable] = [
"username": username,
"password": password,
]
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
let task = URLSession.shared.dataTask(with: request) {data, _, error in
guard let data=data, error == nil else{
return
}
do{
let response = try JSONDecoder().decode(statsServerInfo.self, from: data)
print("SUCCESS: \(response)")
DispatchQueue.main.async{
if (self.tabBarController?.tabBar.selectedItem?.tag == 0){
self.RAMBar.progress = Float(response.out)/100
}
}
}
catch{
print(error)
}
}
task.resume()
}
#IBOutlet var CPUBar: UIProgressView!
#IBOutlet var RAMBar: UIProgressView!
The UIProgressViews are on the first tab. The function automatically runs every 10 seconds with a timer. The error is thrown here: self.RAMBar.progress = Float(response.out)/100
The error happens only if I switch tabs, then I tried putting an if, to check the tab and now, with this code, it happens when, after I switch tabs, I return on the first tab. The exact output is:
SUCCESS: statsServerInfo(ok: true, out: 4)
ServiceTOOLS_Control/ViewController.swift:307: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
2022-04-14 14:52:59.783278+0200 ServiceTOOLS Control[4837:261227] ServiceTOOLS_Control/ViewController.swift:307: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
Here is an example to use UITabBarController.
I have created two ViewControllers and add a UIProgressView in one UIViewController. Here is the code of ViewController.swift file.
import UIKit
class ViewController: UIViewController {
var timer: Timer!
var progressBarValue: Float = 0
let progressBar: UIProgressView = {
let progressView = UIProgressView(progressViewStyle: .bar)
progressView.translatesAutoresizingMaskIntoConstraints = false
progressView.progress = 0
progressView.trackTintColor = .lightGray
progressView.tintColor = .blue
return progressView
}()
override func viewDidLoad() {
super.viewDidLoad()
print("Main VC")
view.backgroundColor = .white
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateProgressBar), userInfo: nil, repeats: true)
view.addSubview(progressBar)
progressBar.center = view.center
}
#objc func updateProgressBar() {
print("Timer called")
progressBarValue += 0.01
progressBar.progress = progressBarValue
}
}
And this is the code of another ViewController namely SecondViewController.swift
import UIKit
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("Second VC")
view.backgroundColor = .cyan
}
}
Now you have to add these ViewControllers to UITabBarController. I have create a class to programatically add those ViewController. Here is the code of MainTabBarController.swift file
import UIKit
class MainTabViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let mainVC = ViewController()
mainVC.tabBarItem = UITabBarItem(title: "Tab 1", image: UIImage(systemName: "circle"), selectedImage: UIImage(systemName: "circle.fill"))
let secondVC = SecondViewController()
secondVC.tabBarItem = UITabBarItem(title: "Tab 2", image: UIImage(systemName: "square"), selectedImage: UIImage(systemName: "square.fill"))
viewControllers = [mainVC, secondVC]
}
}
Now i set this MainTabBarController as rootViewController programatically in SceneDelegate. It's absolutely ok if you are using Storyboard. Just Set TabBarController as InitialViewController.
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
window.rootViewController = MainTabViewController()
self.window = window
window.makeKeyAndVisible()
}
}
I have tested this code. The UIProgressView in ViewController class is updating even if i move to SecondViewController in MainTabBarController. Hope you get the idea.

When minimizing a view, root controller contents also minimize swift

Hi there I am trying to recreate apple musics miniplayer controller. There is a view that shows details about the song playing such as the song name, artist name, cover art and so forth like apple music. When a user clicks the dismiss button on the top of that controller, it minimizes it to a view just above the tabBar revealing the rootview behind the view. The only problem is that my code is causing an issue that when the view is minimized, instead of minimizing the view that shows the information about the current song being played, it minimizes all the views and leaves just a black screen. I'm not sure what is causing the issue but I will provide the code for my tabBar controller which houses the code to minimize and maximize the view and then the other controller which calls the function created in the tabBar controller to minimze and maximize the view as well as screen shots of what is happening. Thank you for taking the time to look at this. If anything is unclear please let me know.
TabBarController Code:
import Foundation
import UIKit
import Firebase
class TabBarController: UITabBarController {
var user: User? {
didSet {
guard let nav = viewControllers?[0] as? UINavigationController else { return }
guard let feed = nav.viewControllers.first as? FeedController else { return }
feed.user = user
}
}
override func viewDidLoad() {
super.viewDidLoad()
fetchUser()
setupDetailsPlayerView()
// perform(#selector(minimizePlayerDetails), with: nil, afterDelay: 1)
// perform(#selector(maximizePlayerDetails), with: nil, afterDelay: 1)
}
#objc func minimizePlayerDetails() {
maximizeTopAnchorConstraint.isActive = false
minimizeTopAnchorConstraint.isActive = true
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
// self.view.layoutIfNeeded()
})
}
#objc func maximizePlayerDetails() {
maximizeTopAnchorConstraint.isActive = true
maximizeTopAnchorConstraint.constant = 0
minimizeTopAnchorConstraint.isActive = false
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
})
}
func fetchUser() {
guard let uid = Auth.auth().currentUser?.uid else { return }
UserService.shared.fetchUser(uid: uid) { user in
self.user = user
}
}
let playerDetailsView = PlayerDetailController.initFromNib()
var maximizeTopAnchorConstraint: NSLayoutConstraint!
var minimizeTopAnchorConstraint: NSLayoutConstraint!
fileprivate func setupDetailsPlayerView() {
print("setting up details view")
// view.addSubview(playerDetailsView)
view.insertSubview(playerDetailsView, belowSubview: tabBar)
playerDetailsView.translatesAutoresizingMaskIntoConstraints = false
maximizeTopAnchorConstraint = playerDetailsView.topAnchor.constraint(equalTo: view.topAnchor, constant: view.frame.height)
maximizeTopAnchorConstraint.isActive = true
minimizeTopAnchorConstraint = playerDetailsView.topAnchor.constraint(equalTo: tabBar.topAnchor, constant: -64)
// minimizeTopAnchorConstraint.isActive = true
playerDetailsView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
playerDetailsView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
playerDetailsView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
}
function being called in songcontroller:
#IBAction func dismissTapped(_ sender: Any) {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let sceneDelegate = windowScene.delegate as? SceneDelegate
else {
return
}
let viewController = TabBarController()
sceneDelegate.window?.rootViewController = viewController
viewController.minimizePlayerDetails()
print("clicked")
self.removeFromSuperview()
}
Screenshots of what is happening:
Normal View:
Minimized View:
If you've created the TabBarController by storyboard then the empty initialization you've provided won't work. You need to instantiate the UIViewController from the storyboard. This seems to be the reason why you're getting an empty UITabBarController when setting the rootViewController. Modify your button action dismissTapped like this:
#IBAction func dismissTapped(_ sender: Any) {
//...
let viewController = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: "TabBarController") as? TabBarController
sceneDelegate.window?.rootViewController = viewController
//...
}

How do I reload UIScrollView containing ViewControllers to add/remove view controllers

I have a scrollView that contains a dynamic amount of WeatherViewControllers each displaying the weather data of a different city the user has saved. The user can segue from the WeatherViewControllers to a CityListViewController. Where they can add and remove cities from their list which in turn should add and remove WeatherViewControllers from the scrollView upon dismissing the CityListViewController, this is where I am running into a problem.
Currently I am trying to use a protocol to call viewDidLoad in the scrollViewController upon dismissing the CityListViewController but am getting an error:
Fatal error: Unexpectedly found nil while unwrapping an Optional
value: file)
when it gets to:
let weatherScreen = storyboard!.instantiateViewController(identifier: "View Controller") as! ViewController
Side Note: Upon initially opening the app the scrollView loads properly with all the correct WeatherViewControllers in the UIScrollView and the correct cities in the list.
class ScrollViewController: UIViewController, ScrollReloadProtocol {
func reloadScrollView() {
print("SCROLL RELOADED!!!!!*******")
self.viewDidLoad()
}
#IBOutlet weak var totalScrollView: UIScrollView!
var pages = [ViewController]()
var x = 0
var weatherScreensArray = [SavedCityEntity]()
var weatherScreenStringArray = [String]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var horizString = "H:|[page1(==view)]"
let defaults = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
//userDefaults used to keep track of which screen is which to put different cities on different viewControllers
defaults.set(0, forKey: "screenNumber")
//load cities to get number of cities saved
loadCities()
var views : [String: UIView] = ["view": view]
//create all weatherWeatherControllers
while x <= weatherScreensArray.count {
pages.append(createAndAddWeatherScreen(number: x))
weatherScreenStringArray.append("page\(x+1)")
views["\(weatherScreenStringArray[x])"] = pages[x].view
let addToHoriz = "[\(weatherScreenStringArray[x])(==view)]"
horizString.append(addToHoriz)
x+=1
}
horizString.append("|")
let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|[page1(==view)]|", options: [], metrics: nil, views: views)
let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: horizString, options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views)
NSLayoutConstraint.activate(verticalConstraints + horizontalConstraints)
}
//Function to create and add weatherViewController
func createAndAddWeatherScreen(number: Int) -> ViewController {
defaults.set(number, forKey: "screenNumber")
let weatherScreen = storyboard!.instantiateViewController(identifier: "View Controller") as! ViewController
weatherScreen.view.translatesAutoresizingMaskIntoConstraints = false
totalScrollView.addSubview(weatherScreen.view)
addChild(weatherScreen)
weatherScreen.didMove(toParent: self)
return weatherScreen
}
}
You could try something like this:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let weatherScreen = storyboard.instantiateViewController(withIdentifier: "View Controller") as! ViewController
It's also probably better to use one word for the identifier: "ViewController", and give it the same name as the class. Make sure to set these values also in your actual storyboard.

Swift - Dismiss Keyboard and call TouchUpInside Button Simultaneously not working when using UIKeyboardWillChangeFrame

I am using swift and having issues with TouchUpInside: if I'm using UIKeyboardWillChangeFrame or UIKeyboardWillShow/UIKeyboardWillHide, & the keyboard is showing, & the button I'm trying to press is behind the keyboard when keyboard is shown initially. (If I scroll down to the button till visible and press, no touchUpInside called).
TouchDown seems to work consistently whether the keyboard is showing or not, but TouchUpInside is not called. If the button is above the top of the keyboard when the keyboard is initially shown, TouchUpInside works. I'm using keyboardNotification to set the height of a view below my scrollView in order to raise up my scrollView when keyboard is showing. From what I can see it's only usually when the button is the last element in the scrollView (and therefore likely to be behind the keyboard when keyboard shown).
#IBOutlet var keyboardHeightLayoutConstraint: NSLayoutConstraint?
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var saveButton: UIButton!
#IBAction func saveTouchUpInside(_ sender: UIButton) {
print("touchupinside = does not work")
}
#objc func saveTouchDown(notification:NSNotification){
print("touchdown = works")
}
viewWillAppear:
textField.delegate = self
NotificationCenter.default.addObserver(self,selector:#selector(self.keyboardNotification(notification:)),name:
NSNotification.Name.UIKeyboardWillChangeFrame,object: nil)
self.saveButton.addTarget(self, action:#selector(ViewController.saveTouchDown(notification:)), for: .touchDown)
deinit {
NotificationCenter.default.removeObserver(self)
}
#objc func keyboardNotification(notification: NSNotification) {
if let userInfo = notification.userInfo {
let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let endFrameY = endFrame?.origin.y ?? 0
let duration:TimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIViewAnimationOptions.curveEaseInOut.rawValue
let animationCurve:UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
if endFrameY >= UIScreen.main.bounds.size.height {
self.keyboardHeightLayoutConstraint?.constant = 0.0
} else {
self.keyboardHeightLayoutConstraint?.constant = endFrame?.size.height ?? 0.0
}
UIView.animate(withDuration: duration, delay: TimeInterval(0),options: animationCurve, animations: { self.view.layoutIfNeeded() }, completion: nil)
}
}
I would like to dismiss the keyboard and call saveTouchUpInside at the same time, without using TouchDown.
I abstract the keyboard interaction as a separate class so that my controllers do not get bloated(also follows separation of concerns). Here is the keyboard manager class that I use.
import UIKit
/**
* To adjust the scroll view associated with the displayed view to accommodate
* the display of keyboard so that the view gets adjusted accordingly without getting hidden
*/
class KeyboardManager {
private var scrollView: UIScrollView
/**
* -parameter scrollView: ScrollView that need to be adjusted so that it does not get clipped by the presence of the keyboard
*/
init(scrollView: UIScrollView) {
self.scrollView = scrollView
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self,
selector: #selector(adjustForKeyboard),
name: UIResponder.keyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self,
selector: #selector(adjustForKeyboard),
name: UIResponder.keyboardDidChangeFrameNotification, object: nil)
}
/**
* Indicates that the on-screen keyboard is about to be presented.
* -parameter notification: Contains animation and frame details on the keyboard
*
*/
#objc func adjustForKeyboard(notification: Notification) {
guard let containedView = scrollView.superview else { return }
let userInfo = notification.userInfo!
let keyboardScreenEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let keyboardViewEndFrame = containedView.convert(keyboardScreenEndFrame, to: containedView.window)
let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber
let rawAnimationCurveValue = (userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as! NSNumber).uintValue
UIView.animate(withDuration: TimeInterval(truncating: duration),
delay: 0,
options: [UIView.AnimationOptions(rawValue: rawAnimationCurveValue)],
animations: {
if notification.name == UIResponder.keyboardWillHideNotification {
self.scrollView.contentInset = UIEdgeInsets.zero
} else {
self.scrollView.contentInset = UIEdgeInsets(top: 0,
left: 0,
bottom: keyboardViewEndFrame.height,
right: 0)
}
self.scrollView.scrollIndicatorInsets = self.scrollView.contentInset
},
completion: nil)
}
deinit {
let notificationCenter = NotificationCenter.default
notificationCenter.removeObserver(self)
}
}
Its usage is like this
create a reference to the keyboard manager
private var keyboardManager: KeyboardManager!
and assign the keyboard manager class like below in viewDidLoad where self.scrollView is the scrollView that you are working with
self.keyboardManager = KeyboardManager(scrollView: self.scrollView)
This should take care of the issue. If that does not work, probably a sample project might help to take a deep dive into that.

app crashed due to UICollectionViewController can't be presentingViewController

My App is crashed when I try to call Custom UIPresentationController from UICollectionViewController
#IBAction func showSettingsMenu(sender: UIBarButtonItem) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let mvc = storyboard.instantiateViewControllerWithIdentifier(Constants.SettingsViewControllerId) as? SettingsViewController {
mvc.modalPresentationStyle = .Custom
mvc.transitioningDelegate = self
presentViewController(mvc, animated: true, completion: nil)
}
}
here is my Custom Presentation View Controller:
class SpecialSizePresentationController: UIPresentationController {
override func frameOfPresentedViewInContainerView() -> CGRect {
guard let height = containerView?.bounds.height else {
return CGRectZero
}
guard let width = containerView?.bounds.width else {
return CGRectZero
}
let y = height * 0.6
let frameHeight = height * 0.4
return CGRectMake(8, y - 8 , width - 8 - 8 , frameHeight)
}
}
here is UIViewControllerTransitioningDelegate protocol implementation in my UICollectionViewController:
func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController? {
return SpecialSizePresentationController(presentedViewController: presented, presentingViewController: presenting)
}
and app is crashed in return place with error (due to presenting is nil):
fatal error: unexpectedly found nil while unwrapping an Optional value
Is it possible to present custom Presentation Controller from UICollectionViewController?
How it can be implemented?
I solved this issue, using source instaed of presenting and error was gone