oğuz
oğuz

Reputation: 180

Unable to Change UILabel.text from another View Controller, UILabel gets nil value

I'm a newbie programmer and trying to create a Single View weather application. My goal is editing dateLbl1 created in WeeklyWeatherViewController from HomeViewController.

I wrote my code like this :

let WeeklyWeatherView = WeeklyWeatherViewController()
...
WeeklyWeatherView.dateLbl1.text = nextDayShort[0].uppercased()

But the label gets "nil" value.

Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

(nextDayShort is not nil, and I connected UILabel to Storyboard)

Searched the web for my issue and tried calling ViewController as :

var swipePage: WeeklyWeatherViewController = UIStoryboard(name: "main", bundle: nil).instantiateViewControllerWithIdentifier("MyStoryboard")

I get this error :

Thread 1:EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

I read about passing data between View Controllers but couldn't use them in my code at all. How can I fix this issue? Thank you for any help!

HomeViewController.swift

import UIKit
import Foundation
import CoreLocation

let DateView = DateViewController()
let WeatherView = WeatherViewController()
let WeeklyWeatherView = WeeklyWeatherViewController()



var currentHour : String?
var currentDay: String?
var nextDayShort = [String]()
var listCount = 0

class HomeViewController: UIViewController, CLLocationManagerDelegate {
    @IBOutlet var backgroudView: UIView!
    @IBOutlet weak var daysView: UIView!
    
    @IBOutlet weak var timeView: UIView!
    @IBOutlet weak var splashImage: UIImageView!
    
    @IBOutlet weak var weatherView: UIView!
    @IBOutlet weak var middleView: UIView!
    @IBOutlet weak var rightView: UIView!
    @IBOutlet weak var leftView: UIView!
    @IBOutlet weak var middleHourBGView: UIView!
    
    var locationManager: CLLocationManager?
    
    override func viewDidAppear(_ animated: Bool) {
        
        locationManager = CLLocationManager()
        locationManager?.delegate = self
        locationManager?.requestAlwaysAuthorization()
        locationManager?.desiredAccuracy = kCLLocationAccuracyBest
        locationManager?.startUpdatingLocation()
        
        if CLLocationManager.locationServicesEnabled() {
           switch CLLocationManager.authorizationStatus() {
                case .restricted, .denied:
                    let alert = UIAlertController(title: "Locations servies are disabled!", message: "Please go to settings and allow location services or the application can't fetch weather data.", preferredStyle: .alert)
                   
                    let settingsAction = UIAlertAction(title: "Settings", style: .default) { (_) -> Void in
                        guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
                            return
                        }

                        if UIApplication.shared.canOpenURL(settingsUrl) {
                            UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
                        }
                    }
                   
                   alert.addAction(settingsAction)
                   let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil)
                   alert.addAction(cancelAction)

                   self.present(alert, animated: true, completion: nil)
            
                case .authorizedAlways, .authorizedWhenInUse:
                   print("Access")
            
                @unknown default:
                break
            }
        } else {
            print("Location services are not enabled")
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        styleUI()
    }
    
    private func styleUI(){
        daysView.layer.cornerRadius = 10
        daysView.layer.shadowOpacity = 0.5
        daysView.layer.shadowOffset = .zero
        daysView.layer.shadowRadius = 10
        
        middleView.layer.cornerRadius = 25
        middleHourBGView.layer.cornerRadius = 10
        
        let now = Date()
        let formatter = DateFormatter()
        let hour = Calendar.current.component(.hour, from: Date())
        
        formatter.timeZone = TimeZone.current
        formatter.dateFormat = "MMMM dd,YYYY EEEE"
        let dateString = formatter.string(from: now)
        DateView.dateLbl?.text = dateString
        currentHour = String(hour)
        
        
        let formatter2 = DateFormatter()
        
        for n in 1..<6 {
            var dayComponent = DateComponents()
            dayComponent.day = n
            let theCalendar = Calendar.current
            let nextDate = theCalendar.date(byAdding: dayComponent, to: Date())
            formatter2.dateFormat = "EE"
            let nextDaySh = formatter2.string(from: nextDate! as Date)
            nextDayShort.append(nextDaySh)
        }
        
        DispatchQueue.main.async {
            WeeklyWeatherView.dateLbl1.text = nextDayShort[0].uppercased()
            WeeklyWeatherView.dateLbl2.text = nextDayShort[1].uppercased()
            WeeklyWeatherView.dateLbl3.text = nextDayShort[2].uppercased()
            WeeklyWeatherView.dateLbl4.text = nextDayShort[3].uppercased()
            WeeklyWeatherView.dateLbl5.text = nextDayShort[4].uppercased()
        }
        
        if (hour > 5 && hour < 21) {
            backgroudView.backgroundColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
            timeView.backgroundColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
            daysView.layer.shadowColor = #colorLiteral(red: 0.3333333433, green: 0.3333333433, blue: 0.3333333433, alpha: 1)
        } else {
            backgroudView.backgroundColor = #colorLiteral(red: 0.0862745098, green: 0.09411764706, blue: 0.3058823529, alpha: 1)
            timeView.backgroundColor = #colorLiteral(red: 0.0862745098, green: 0.09411764706, blue: 0.3058823529, alpha: 1)
            DateView.cityLbl.textColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
            DateView.dateLbl.textColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
            DateView.tempLbl.textColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
            daysView.layer.shadowColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
            WeatherView.leftHourLbl.textColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
            WeatherView.leftTempLbl.textColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
            WeatherView.rightHourLbl.textColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
            WeatherView.rightTempLbl.textColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
            WeatherView.middleTempLbl.textColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        let location:CLLocationCoordinate2D = manager.location!.coordinate
        weather.getWeather(lat: location.latitude, lon: location.longitude) { weather in
            DispatchQueue.main.async {
                DateView.cityLbl.text  = weather.city.name
                DateView.tempLbl.text  = "\(Int(round(weather.list[0].main.temp)) - 273)°"
                WeeklyWeatherView.tempLbl1.text = "\(Int(round(weather.list[1].main.temp)) - 273)°"
                WeeklyWeatherView.tempLbl2.text = "\(Int(round(weather.list[2].main.temp)) - 273)°"
                WeeklyWeatherView.tempLbl3.text = "\(Int(round(weather.list[3].main.temp)) - 273)°"
                WeeklyWeatherView.tempLbl4.text = "\(Int(round(weather.list[4].main.temp)) - 273)°"
                WeeklyWeatherView.tempLbl5.text = "\(Int(round(weather.list[5].main.temp)) - 273)°"
                
                self.splashImage.isHidden = true
            }
        }

    }
}


WeeklyWeatherViewController.swift

import UIKit
import Foundation

let storyBoardEdit = StoryboardEditor()
let weather = WeatherAPI()

class WeeklyWeatherViewController: UIView {
    @IBOutlet weak var dateLbl1: UILabel!
    @IBOutlet weak var dateLbl2: UILabel!
    @IBOutlet weak var dateLbl3: UILabel!
    @IBOutlet weak var dateLbl4: UILabel!
    @IBOutlet weak var dateLbl5: UILabel!
    
    @IBOutlet weak var tempLbl1: UILabel!
    @IBOutlet weak var tempLbl2: UILabel!
    @IBOutlet weak var tempLbl3: UILabel!
    @IBOutlet weak var tempLbl4: UILabel!
    @IBOutlet weak var tempLbl5: UILabel!
    
    @IBOutlet weak var weatherImg1: UIImageView!
    @IBOutlet weak var weatherImg2: UIImageView!
    @IBOutlet weak var weatherImg3: UIImageView!
    @IBOutlet weak var weatherImg4: UIImageView!
    @IBOutlet weak var weatherImg5: UIImageView!
    
    
    func fillUI(){
        
        dateLbl1.text = nil
        
        var dayId = [String]()
        for x in 1..<6 {
            dayId.append("03d")
            let dayIdInt = Int(dayId[x-1])!
            let imageIDName = storyBoardEdit.editWeatherIcons(dayIDInt: dayIdInt)
            
            switch x {
            case 1:
                DispatchQueue.main.async {
                    self.weatherImg1.image = UIImage(named: imageIDName)
                }
            case 2:
                DispatchQueue.main.async {
                    self.weatherImg2.image = UIImage(named: imageIDName)
                }
            case 3:
                DispatchQueue.main.async {
                    self.weatherImg3.image = UIImage(named: imageIDName)
                }
            case 4:
                DispatchQueue.main.async {
                    self.weatherImg4.image = UIImage(named: imageIDName)
                }
            case 5:
                DispatchQueue.main.async {
                    self.weatherImg5.image = UIImage(named: imageIDName)
                }
            default:
                break
            }
        }
    }

}

Upvotes: 0

Views: 307

Answers (2)

hfelpp
hfelpp

Reputation: 403

Do this changes:

  • In your Storyboard (or Xib), update the class of your weatherView to WeeklyWeatherViewController and reset the outlet. You should get it in this way:
@IBOutlet weak var weatherView: WeeklyWeatherViewController!
  • Delete the following line:
let WeeklyWeatherView = WeeklyWeatherViewController()
  • In your styleUI() method replace the lines using WeeklyWeatherView by this:
DispatchQueue.main.async {
    weeklyWeatherView.dateLbl1.text = nextDayShort[0].uppercased()
    weeklyWeatherView.dateLbl2.text = nextDayShort[1].uppercased()
    weeklyWeatherView.dateLbl3.text = nextDayShort[2].uppercased()
    weeklyWeatherView.dateLbl4.text = nextDayShort[3].uppercased()
    weeklyWeatherView.dateLbl5.text = nextDayShort[4].uppercased()
}

In your locationManager() function, replace the lines using WeeklyWeatherView by this:

weeklyWeatherView.tempLbl1.text = "\Int(round(weather.list[1].main.temp)) - 273)º"
weeklyWeatherView.tempLbl2.text = "\Int(round(weather.list[2].main.temp)) - 273)º"
weeklyWeatherView.tempLbl3.text = "\Int(round(weather.list[3].main.temp)) - 273)º"
weeklyWeatherView.tempLbl4.text = "\Int(round(weather.list[4].main.temp)) - 273)º"
weeklyWeatherView.tempLbl5.text = "\Int(round(weather.list[5].main.temp)) - 273)º"

See that I'm using the newly created outlet in both cases, right?

Upvotes: 2

Tapas Pal
Tapas Pal

Reputation: 7207

The problem is the way you have initialized the WeeklyWeatherViewController

let WeeklyWeatherView = WeeklyWeatherViewController()

This view has an associated xib from where you have created the connection. Now When you are initializing your WeeklyWeatherViewController class that time your xib view is not been initialized.

You need to load the nib file in the init method in your WeeklyWeatherViewController.

class WeeklyWeatherViewController: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
    
        let contentView = Bundle.main.loadNibNamed("your_nib", owner: self, options: nil)?.first as? UIView
        addSubview(contentView!)
    
        // set constraints for contentView.
    }

    convenience init() {
        let defaultFrame = //provide your default frame
        self.init(frame: defaultFrame)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

So the thing is when you have an associated xib file, in short when you are supplying your view from outside you need to initialize that external view and attach it to your class. That way your view will still remain the memory and you can access all the connected IBOutlet.

Upvotes: 1

Related Questions