Initializing a variable initialized from IBoutlet variables for a view controller? - swift

I am trying to initialize the variable deckEditorModel using termAndDefTable and copyNPasteTable as inputs. I don't want deckEditorModel to be an optional variable as a know that I would not be able to use that tactic in C. I tried putting deckEditorModel in the init methods for the view controller but it seems that the required termNDefTable: termAndDefTable, otherTable: OtherTable are not available to use as imputs during the init as they are IBOutlets. How would someone go about this
import Cocoa
class DeckEditorViewController: NSViewController {
#IBOutlet weak var importToolsView: NSView!
#IBOutlet weak var termAndDefTable: NSTableView!
#IBOutlet weak var otherTable: NSTableView!
var deckEditorModel:DeckEditorModel?
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
self.view.wantsLayer = true
}
override func awakeFromNib() {
deckEditorModel = DeckEditorModel(termNDefTable: termAndDefTable, otherTable: otherTable)
}
I tried adding it to the viewDidLoad but apparently that is to late to define deckEditorModel. The below error dissappears when deckEditorModel is an optional

You could initialize the variable lazily
lazy var deckEditorModel : DeckEditorModel = {
return DeckEditorModel(termNDefTable: self.termAndDefTable, otherTable: self.otherTable)
}()

Place
deckEditorModel = DeckEditorModel(termNDefTable: termAndDefTable, otherTable: otherTable)
In viewDidLoad, then the outlets will have been set.

Related

Swift - Can't access struct properties

Learning swift but a little confused as to the below. I have struct created and instantiated, but I cannot access the property of the struct inside of my view controller class unless the struct is inside one my my class methods. See below, why would this be the case?
class WeatherViewController: UIViewController, UITextFieldDelegate, WeatherManagerDelegate {
//create new weather manager struct
var weatherManager = WeatherManager()
//can't access property here, but I can access it inside of functions within this
//class, see below under viewDidLoad()
weatherManager.delegate = self
#IBOutlet weak var conditionImageView: UIImageView!
#IBOutlet weak var temperatureLabel: UILabel!
#IBOutlet weak var cityLabel: UILabel!
#IBOutlet weak var searchTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
//can access property here
weatherManager.delegate = self
}
The problem isn't where the WeatherManager object is declared and created. The problem is that this line:
weatherManager.delegate = self
is a command (technically a statement), not a declaration. (The line above it is a declaration, one that happens to also set the weatherManager property's default value). This is a pretty universal rule in most languages in the C++/Java family -- see short C++ example below. A command (statement) must be inside some method (or function, in non-OOP programming), not at the top level of a file or class. In Swift, actions like setting an object's delegate would typically go in the view controller's viewDidLoad.
int x = 0; // legal: declaring a global variable
x = x + 42; // NOT legal: this is a statement, not a declaraiton
int main()
{
x = x + 42; // legal: now we're inside a function
return 0;
}

Why array's append method cannot be used in viewController?

I am beginner of swift. I tried to use array's append method in my code but it doesn't work. How should I implement the array correctly?
The error messages:
Swift Compiler Error Group
ViewController.swift:16:5: Expected declaration
ViewController.swift:11:7: In declaration of 'ViewController'
I tried to use array's append method in my code but it doesn't work.
import UIKit
class ViewController: UIViewController { //Error msg: In declaration of 'ViewController'
#IBOutlet weak var dice: UIImageView!
#IBOutlet weak var dice2: UIImageView!
var dices : [String] = []
dices.append("Hi") //Error: Expected declaration
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func rollPressed(_ sender: UIButton) {
dice.image = UIImage(named: "dice3")
}
}
I expect I can add "hi" into the array dices.
You should call the append inside a function after the vc is fully initated
class ViewController: UIViewController { //Error msg: In declaration of 'ViewController'
#IBOutlet weak var dice: UIImageView!
#IBOutlet weak var dice2: UIImageView!
var dices : [String] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
dices.append("Hi") // e.x here
}
#IBAction func rollPressed(_ sender: UIButton) {
dice.image = UIImage(named: "dice3")
}
}
Or replace
var dices : [String] = []
with
var dices = ["Hi"]
SH_Khan is right. I'll explain why though.
When defining a class, the first level of indentation is only for its methods and properties, aka func, var, and let. (You can also define other classes/structs/enums in there too)
Calling those functions or system functions like Array.append() or print("dog sweat") must happen inside of another function. The reason why is that your application's live logic is literally just functions all the way down. No function gets called unless it's inside of another function first. (The only exceptions are Swift's quick and dirty initializations like setting a default value to a var outside of an init() { } or another function.)
A dog doesn't wake up from its nap unless you make some noise. It won't do it on its own. (crappy metaphor, but yeah)
I hope that made any sense.

How can I refer to self during initialization when using IBOutlets for UIViews?

I have this custom UITableViewCell class backing my cells in a table view (I learned this approach from a talk by Andy Matuschak who worked at Apple on the UIKit team).
In the project I'm trying to apply this to now, I'm having a problem initializing the class because of a couple of #IBOutlets that are linked to UIView elements that won't ever have values like the UILabels get upon initialization:
class PublicationTableViewCell: UITableViewCell {
#IBOutlet weak var publicationTitle: UILabel!
#IBOutlet weak var authorName: UILabel!
#IBOutlet weak var pubTypeBadgeView: UIView!
#IBOutlet weak var pubTypeBadge: UILabel!
#IBOutlet weak var topHighlightGradientStackView: UIStackView!
#IBOutlet weak var bottomBorder: UIView!
struct ViewData {
let publicationTitle: UILabel
let authorName: UILabel
let pubTypeBadge: UILabel
}
var viewData: ViewData! {
didSet {
publicationTitle = viewData.publicationTitle
authorName = viewData.authorName
pubTypeBadge = viewData.pubTypeBadge
}
// I have #IBOutlets linked to the views so I can do this:
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
super.setHighlighted(highlighted, animated: animated)
if isSelected || isHighlighted {
topHighlightGradientStackView.isHidden = true
bottomBorder.backgroundColor = UIColor.lightGrey1
} else {
topHighlightGradientStackView.isHidden = false
}
}
}
extension PublicationTableViewCell.ViewData {
init(with publication: PublicationModel) {
// Xcode complains here about referring to the properties
// on the left before all stored properties are initialized
publicationTitle.text = publication.title
authorName.text = publication.formattedAuthor.name
pubTypeBadge.attributedText = publication.pubTypeBadge
}
}
I then initialize it in cellForRow by passing in a publication like this:
cell.viewData = PublicationTableViewCell.ViewData(with: publication)
Xcode is complaining that I'm using self in init(with publication: PublicationModel) before all stored properties are initialized which I understand, but I can't figure out how to fix.
If these weren't UIView properties I might make them optional or computed properties perhaps, but because these are IBOutlets, I think they need to be implicitly unwrapped optionals.
Is there some other way I can get this to work?
First of all:
struct ViewData {
let publicationTitle: String
let authorName: String
let pubTypeBadge: NSAttributedString
}
Then simply:
extension PublicationTableViewCell.ViewData {
init(with publication: PublicationModel)
publicationTitle = publication.title
authorName = publication.formattedAuthor.name
pubTypeBadge = publication.pubTypeBadge
}
}
It's a data model, it shouldn't hold views, it should hold only data.
Then:
var viewData: ViewData! {
didSet {
publicationTitle.text = viewData.publicationTitle
authorName.text = viewData.authorName
pubTypeBadge.attributedString = viewData.pubTypeBadge
}
}
However, I think it would be simpler if you just passed PublicationModel as your cell data. There is no reason to convert it to another struct.
var viewData: PublicationModel! {
didSet {
publicationTitle.text = viewData.publicationTitle
authorName.text = viewData.authorName
pubTypeBadge.attributedString = viewData.pubTypeBadge
}
}

Variable in label Name - Swift

Is it possible to use a variable in a label name.
For example, my label is called button1text and I have a variable var x = 1.
Is there a way to do thisbutton(x)text?
No. You can not have variable name in identifier.
Variable names are evaluated at compile time, so no, it's not possible (at runtime).
Alternatively use an array or assign tags to the labels and get the label with viewWithTag
You can use reflection with Mirror. The capabilities are very limited in the context you want but you can try something like:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var button1: UIButton!
#IBOutlet weak var button2: UIButton!
#IBOutlet weak var button3: UIButton!
#IBAction func didTapGoButton(_ sender: Any) {
let mirror = Mirror(reflecting: self)
for child in mirror.children {
if let v = child.label, v == "button2" {
(child.value as! UIButton).titleLabel?.text = "changed"
}
}
}
}

Initializing class object swift 3

i have a problem while I'm initializing object of some class. What is wrong with that? (I can upload all my code but it is large if needed)
Edit:
My view controller code:
import UIKit
class ViewController: UIViewController{
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var answerStackView: UIStackView!
// Feedback screen
#IBOutlet weak var resultView: UIView!
#IBOutlet weak var dimView: UIView!
#IBOutlet weak var resultLabel: UILabel!
#IBOutlet weak var feedbackLabel: UILabel!
#IBOutlet weak var resultButton: UIButton!
#IBOutlet weak var resultViewBottomConstraint: NSLayoutConstraint!
#IBOutlet weak var resultViewTopConstraint: NSLayoutConstraint!
var currentQuestion:Question?
let model = QuizModel()
var questions = [Question]()
var numberCorrect = 0
override func viewDidLoad() {
super.viewDidLoad()
model.getQuestions()
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setAll(questionsReturned:[Question]) {
/*
// Do any additional setup after loading the view, typically from a nib.
// Hide feedback screen
dimView.alpha = 0
// Call get questions
questions = questionsReturned
// Check if there are questions
if questions.count > 0 {
currentQuestion = questions[0]
// Load state
loadState()
// Display the current question
displayCurrentQuestion()
}
*/
print("Called!")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
My QuizModel code:
import UIKit
import FirebaseDatabase
class QuizModel: NSObject {
override init() {
super.init()
}
var ref:FIRDatabaseReference?
var test = [[String:Any]]()
var questions = [Question]()
weak var prot:UIPageViewControllerDelegate?
var first = ViewController()
func getQuestions(){
getRemoteJsonFile()
}
func pars(){
/*let array = test
var questions = [Question]()
// Parse dictionaries into Question objects
for dict in array {
// Create question object
let q = Question()
// Assign question properties
q.questionText = dict["question"] as! String
q.answers = dict["answers"] as! [String]
q.correctAnswerIndex = dict["correctIndex"] as! Int
q.module = dict["module"] as! Int
q.lesson = dict["lesson"] as! Int
q.feedback = dict["feedback"] as! String
// Add the question object into the array
questions += [q]
}
*/
//Protocol setAll function
first.setAll(questionsReturned: questions)
}
func getRemoteJsonFile(){
ref = FIRDatabase.database().reference()
ref?.child("Quiz").observeSingleEvent(of: .value, with: { (snapchot) in
print("hey")
let value = snapchot.value as? [[String:Any]]
if let dict = value {
self.test = dict
self.pars()
}
})
}
This isn't my all code but I think that is the most important part. In QuizModel code I'm reskining my code from getting json file to get data from firebase so you can see names of functions like 'getRemoteJSONFile' and in parse function parsing json but it isn't an issue of my problem I think
It looks like you're trying to initialize a constant before you've initialized the viewController it lives in. Your have to make it a var and initialize it in viewDidLoad (or another lifecycle method), or make it a lazy var and initialize it at the time that it's first accessed.
The problem boils down to the following:
class ViewController ... {
let model = QuizModel()
}
class QuizModel ... {
var first = ViewController()
}
The variable initializers are called during object initialization. When you create an instance of ViewController, an instance of QuizModel is created but its initialization creates a ViewController which again creates a new QuizModel and so on.
This infinite cycle will sooner or later crash your application (a so called "stack overflow").
There is probably some mistake on your side. Maybe you want to pass the initial controller to QuizModel, e.g.?
class ViewController ... {
lazy var model: QuizModel = {
return QuizModel(viewController: self)
}
}
class QuizModel ... {
weak var viewController: ViewController?
override init(viewController: ViewController) {
self.viewController = viewController
}
}
Also make sure you are not creating ownership cycles (that's why I have used weak).
In general it's a good idea to strictly separate your model classes and your UI classes. You shouldn't pass view controllers (or page view controller delegate) to your model class. If you need to communicate with your controller, create a dedicated delegate for that.