UITextField always nil - ios

I'm updating some example code to the latest version of both Swift (from beta 4 to 1.2) and also the latest version of Couchbase Lite.
I'm having an issue with a UITextField always being nil. I've checked that its connected in the IB / xib file. I've disconnected and reconnected it to the UITextField object addItemTextField.
Its declared as
#IBOutlet weak var addItemTextField: UITextField!
and the relevant functions:
func configureView() {
// Update the user interface for the detail item.
if let detail: AnyObject = self.detailItem {
if let label = self.detailDescriptionLabel {
label.text = detail.description
}
title = detail.title
assert(addItemTextField != nil, "addItemTextField cannot be nil") // <- always hits this assertion
addItemTextField.enabled = true
addImageButton.enabled = true
navigationItem.rightBarButtonItem!.title = "Share"
assert(dataSource != nil, "detail.dataSource not connected")
dataSource!.labelProperty = "title"
dataSource!.query = detail.queryTasks().asLiveQuery()
} else {
title = nil
addImageButton.enabled = false
addItemTextField.enabled = false
}
}
override func viewDidLoad() {
app = UIApplication.sharedApplication().delegate as? AppDelegate
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.configureView()
}
The ViewController its part of is instantiated with
performSegueWithIdentifier("showDetail", sender: self)
(and of course i've checked the identifier is correctly set in the xib file)
Is there something I might have missed here?

Related

How to count how many times all classes are called

I want the user to be able to know how many times they have visited each class. Then add together the totals from each page together to form a group sum. I want to print the total sum in the log file in each of the two view controllers. So just one string should be printed.
class oneV: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
UserDefaults.standard.set(true, forKey: "VC1")
}
}
class twoV: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
UserDefaults.standard.set(true, forKey: "VC2")
}
}
If you mean visited each view controller, when you say visited each class. Then i'd recommend you do it viewDidAppear.
class YourViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let key = String(describing: type(of: self))
let count = UserDefaults.standard.value(forKey: key) as? Int ?? 0
UserDefaults.standard.set(value + 1, forKey: key)
}
}
To make it simpler, you could use an extension on UIViewController.
extension UIViewController {
func updateVisitCount() {
let key = String(describing: type(of: self))
let count = UserDefaults.standard.value(forKey: key) as? Int ?? 0
UserDefaults.standard.set(count + 1, forKey: key)
}
}
Or, if you need this for every view controller that you create, then you can create a base view controller which you would use everywhere instead of UIViewController.
class BaseViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateVisitCount()
}
}
The most automatic solution would be inject the accounting call in viewDidLoad without replacing the original viewDidLoad.
Here demo purpose i've created a sample Playground
import UIKit
import PlaygroundSupport
extension UIViewController {
#objc dynamic func substitutedViewDidAppear() {
print("This is injected code in view did appear")
substitutedViewDidAppear() // it may look like recursive, but it isn't, actually it calls the original `viewDidAppear` method.
}
class func swizzle() {
let originalMethod = class_getInstanceMethod(UIViewController.self, #selector(viewDidAppear(_:)))
let substitutedMethod = class_getInstanceMethod(UIViewController.self, #selector(substitutedViewDidAppear))
if let originalMethod = originalMethod,
let substitutedMethod = substitutedMethod {
print("swizzled")
method_exchangeImplementations(originalMethod, substitutedMethod)
} else {
print("not swizzled")
}
}
}
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let label = UILabel()
label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.text = "Hello World!"
label.textColor = .black
view.addSubview(label)
self.view = view
print("view loaded")
}
}
// Swizzle
UIViewController.swizzle() // call this in #top of didFinishLaunchingWithOptions
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
Output:
swizzled
view loaded
This is injected code in view did appear
Now in the substitutedViewDidAppear upper portion inject your counting code as #Rakesha Shastri Suggested, call the updateVisitCount method inside of substitutedViewDidAppear & place the UIViewController.swizzle() in applicationDidFinishLaunchingWithOptions before creating the root window.
Create a static variable. A static variable is a type of class, not object therefore throughout all objects a variable maybe maintained. I think this example may better explain how this works. Click here
In ViewDidLoad method call this function :
func updateVisitingCounter() {
var counter = UserDefaults.standard.integer(forKey: "firstPageCounter")
counter += 1
UserDefaults.standard.set(counter, forKey: "firstPageCounter")
}
You may set declare variables at project scope "outside of classes"
var vc1Count = 0
class oneV: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
vc1Count = vc1Count+1
}
}
var vc2Count = 0
class twoV: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
vc2Count = vc2Count+1
}
}
you can also declare these variables at a common place.
As per your requirements its kind of Analytics on app usage. You can implement in 2 ways
By storing data with screen visit in local DB and show it on Analysis Page or on summery page.
Sample code for storing Screen details in DB:
==> Create your Entity for Screen capture.
ScreenVisit.
==> Store Data with screen name.
let entity = NSEntityDescription.entity(forEntityName: "ScreenVisit", in: context)
let newVisit = NSManagedObject(entity: entity!, insertInto: context)
newVisit.setValue("HomeScreen", forKey: "screenname")
newVisit.setValue("1", forKey: "visited")
do {
try context.save()
} catch {
print("Failed saving")
}
==> Fetch data where you required.
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "ScreenVisit")
//request.predicate = NSPredicate(format: <Your Filter Logic>)
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
print(data.value(forKey: "screenname") as! String)
print(data.value(forKey: "visited") as! String)
}
} catch {
print("Failed")
}
You can use any 3rd party library like Google analytics, Crashlytics for tracking your user actions.
Ref Links :
Firebase iOS analytics
Crashlytics
but as per my experience 2nd way is more convenient and powerful.
All depends on your requirements.
Hope this will helps you to get your user action captured.

View through IBOutlet or manually created is nil after views hierarchy is loaded

I am creating a macOS app on Xcode 9.2 and i can't figure out why after the views hierarchy is created in viewDidLoad(), i can't use anymore the IBOutlet which is referencing the view in the StoryBoard.
I have found similar question, where they suggest to save the view that i want to update, as a variable, when i am in viewDidLoad() so i can use it later; if so what is the advantage of using an IBOutlet?
Can someone pls explain how to update in this case the content of the PDFView outside viewDidLoad() without getting nil?
The AppDelegate
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
// MARK: - Properties
var filePath: URL?
var result: Int?
var fileObject: Data?
// MARK: - Outlets
#IBOutlet weak var openFileMenuItem: NSMenuItem!
// MARK: - App Life Cycle
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
// MARK: - My Functions
#IBAction func openFile(_ sender: NSMenuItem) {
//Get the window of the app
var window = NSApplication.shared.mainWindow!
// Create NSOpenPanel and setting properties
let panel = NSOpenPanel()
panel.title = "Select file"
panel.allowsMultipleSelection = false;
panel.canChooseDirectories = false;
panel.canCreateDirectories = false;
panel.canChooseFiles = true;
// Filtering file extension
let fileTypes: [String] = ["pdf"]
panel.allowedFileTypes = fileTypes
// Showing OpenPanel to the user for selecting the file
panel.beginSheetModal(for: window) {
(result) in
if result.rawValue == NSFileHandlingPanelOKButton {
// Getting url of the file
self.filePath = panel.urls[0]
print(self.filePath)
// Creating Data Object of the file for creating later the PDFDocument in the controller
do {
if let fileData = self.filePath {
self.fileObject = try Data.init(contentsOf: self.filePath!)
print("\(self.fileObject) ")
}
} catch let error as NSError {
print("Error with file Data Object: \(error)")
}
// Getting the mainViewController
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil)
let mainController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "mainViewControllerID")) as! MainViewController
// Appdelegate call function of mainViewController to update the content of PDFView
mainController.showPdfDocument(fileData: self.fileObject!)
}
}
}
}
The MainViewController:
import Foundation
import Quartz
class MainViewController: NSViewController {
// MARK: - Property
var pdfdocument: PDFDocument?
// MARK: - Outlets
#IBOutlet var mainView: NSView!
#IBOutlet var pdfView: PDFView!
// MARK: - App Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
// MARK: - My Functions
func showPdfDocument(fileData: Data) {
print("appdelegate calling showPdfDocument on mainViewController " )
pdfdocument = PDFDocument(data: fileData)
pdfView.document = pdfdocument
}
}
The Error I Am Getting:
fatal error: unexpectedly found nil while unwrapping an Optional value
I solved the problem with the current code in the AppDelegate. Basically i am using the contentViewController to update the PDFView but i don't know if this is a good practice because i should update the PDFView with its own controller and not with the one of the window.
in the openFile function of the AppleDelegate:
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil)
let mainViewController = NSApplication.shared.mainWindow?.contentViewController as! MainViewController
mainViewController.showPdfDocument(fileData: self.fileObject!)
Reading from the Apple documentation at this link (https://developer.apple.com/documentation/appkit/nswindow/1419615-contentviewcontroller) it says 'Directly assigning a contentView value clears out the root view controller.'. Should I programmatically assign my mainView to the contentView to automatically set my mainViewController as the initial controller?
I have also found that i can set my controller as the initial controller from IB as you can see form this image:
The ideal thing i want to do is to leave the contentViewController as the initial viewController (the default setting in IB) and then instantiate my mainViewController to update the pdfView. The problem is that with instantiation, the new viewController has everything to nil(IBOutlet, vars etc..). Is it the downcasting from contentViewController to mainViewController a good approach to achieve this task?

Referencing IBOutlet in another View Controller

So, I have been having some major trouble figuring this out and I have searched extensively for a solution but I surprisingly could not find one. I am attempting to create a multiple page (5, to be exact) Sign-Up for users.
I'll start off by showing you the layout of page 1 and 5 (since solving that issue will solve the issue for page 2-4):
Sign Up Page #1
Sign Up Page #5
As you may see (from the page control dots), I am using a page view controller to allow users to scroll from page to page. What I am trying to accomplish is giving the user the ability to enter their sign-up information in pages 1-5 before submitting it all at once (which can be located on page 5).
Here is the current code I am using for page #1:
class SignUpInfoViewController: UIViewController {
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Here is the current code I am using for page #5:
class TermsOfUseViewController: UIViewController {
let minPasswordCharCount = 6
#IBAction func signUpAction(_ sender: Any) {
let providedEmailAddress = SignUpInfoViewController().emailTextField.text!
let providedPassword = SignUpInfoViewController().passwordTextField.text!
let trimmedPassword = providedPassword.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if !(validEmail(enteredEmail: providedEmailAddress) && validPassword(enteredPassword: trimmedPassword)) {
invalidCredentialsAlert()
}
else {
FIRAuth.auth()?.createUser(withEmail: providedEmailAddress, password: providedPassword) { user, error in
if error == nil {
FIRAuth.auth()!.signIn(withEmail: providedEmailAddress,
password: providedPassword)
}
else {
let alertController = UIAlertController(title: "Error", message: error?.localizedDescription, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
}
}
}
// Email is valid if it has a standard email format
func validEmail(enteredEmail: String) -> Bool {
let emailFormat = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPredicate = NSPredicate(format:"SELF MATCHES %#", emailFormat)
return emailPredicate.evaluate(with: enteredEmail)
}
// Password is valid if it is not empty or greater than a specified number of characters
func validPassword(enteredPassword: String) -> Bool {
if (enteredPassword != "" && enteredPassword.characters.count >= minPasswordCharCount) {
return true
}
return false
}
In the TermsOfUseViewController class, I am attempting to use the emailTextField and passwordTextField outlets from the SignUpInfoViewController, but I am receiving the following error:
fatal error: unexpectedly found nil while unwrapping an Optional value
I debugged the error and saw that the emailTextField property from SignUpInfoViewController is nil and so force unwrapping it will cause the app to crash (Note: I have correctly connected the IBOutlets to the SignUpInfoViewController, so no issue there).
How can I safely transfer the usage of the IBOutlets from the SignUpInfoViewController class to the TermsOfUseViewController class without it crashing? In other words, how can I make it to where the IBOutlets are no longer nil when I reference them in the TermsOfUseViewController class?
Thank you!
That is a perfect scenario for delegate pattern
protocol SignUpProtocol: class {
func didProvideUserData(username: String ,password: String)
}
In your signup class declare a delegate: public weak var delegate:SignUpProtocol?
I am assuming when the user has provided the require info, they need to press some button to go to the next step: Thus in that button you should raise the delegate
#IBAction func nextButton(sender:UIButton) {
guard let username = usernameTextfield?.text, let password = passwordTextField?.text, else { fatalError("textfields were empty") }
if delegate != nil { // this saying when someone is listening to me, I will expose any method associated to me
delegate?.didProvideUserData(username:username, password:password) // passing the username and password from textfield
}
}
if you don't have a button, then look at property observer, where you could have some property
var didFulfill:Bool? = nil {
didSet {
if didFulfill != nil && didFulfill == true {}
// here you check if your textfields are sets then raise the delegate
}
}
set this property didFulfill = when both textfields are not empty :)
Now in your Terms class, just subscribe to that delegate
class TermsOfUseViewController: UIViewController, SignUpProtocol {
var signUpVc: SignUpInfoViewController?
override func viewDidLoad() {
super.viewDidLoad()
signUpVc = SignUpInfoViewController()
signUpVc?.delegate = self
}
func didProvideUserData(username: String, password:String) {
// there is your data
}
}
You have to take in account that you don't have all references for all UIPageViewControllers all the time. That being said, I would suggest either to keep object in UIPageViewController with updated information or using Singleton Pattern to use it to store info into it and later use it. UIPageViewController are being reused and you might have one before and one after and relying onto having them would be wrong.
You can use UIPageViewController as self.parentViewController or something like that.

Errors while implementing Chromecast in swift 3

I seem to have a problem implementing ChromeCast features in a project of mine.
I have been trying to implement the GCKDeviceScannerListener Singleton Class on a UIViewController, however its delegate methods are not getting called.
The deviceDidComeOnline method of GCKDeviceScannerListener never gets called.
Instead I have a bunch of error displayed by the chromeCast logger as followed:
+[NSMutableDictionary(GCKAdditions) gck_loadFromCacheWithName:] - Device cache file file:///Users/martin/Library/Developer/CoreSimulator/Devices/318D2E15-C4B0-47D2-97AF-CD560A6063AE/data/Containers/Data/Application/C117BB98-88DA-4586-B119-0683DAD82FEB/Library/Caches/gck_nearby_devices.plist doesn't exist.
+[NSMutableDictionary(GCKAdditions) gck_loadFromCacheWithName:] - Device cache file file:///Users/martin/Library/Developer/CoreSimulator/Devices/318D2E15-C4B0-47D2-97AF-CD560A6063AE/data/Containers/Data/Application/C117BB98-88DA-4586-B119-0683DAD82FEB/Library/Caches/gck_network_cache.plist doesn't exist.
scanning started
+[NSMutableDictionary(GCKAdditions) gck_deleteCacheWithName:] - Device cache file file:///Users/martin/Library/Developer/CoreSimulator/Devices/318D2E15-C4B0-47D2-97AF-CD560A6063AE/data/Containers/Data/Application/C117BB98-88DA-4586-B119-0683DAD82FEB/Library/Caches/gck_device_cache.plist doesn't exist.
+[NSMutableDictionary(GCKAdditions) gck_deleteCacheWithName:] - Device cache file file:///Users/martin/Library/Developer/CoreSimulator/Devices/318D2E15-C4B0-47D2-97AF-CD560A6063AE/data/Containers/Data/Application/C117BB98-88DA-4586-B119-0683DAD82FEB/Library/Caches/gck_device_cache_v1.plist doesn't exist.
+[NSMutableDictionary(GCKAdditions) gck_deleteCacheWithName:] - Device cache file file:///Users/martin/Library/Developer/CoreSimulator/Devices/318D2E15-C4B0-47D2-97AF-CD560A6063AE/data/Containers/Data/Application/C117BB98-88DA-4586-B119-0683DAD82FEB/Library/Caches/gck_device_cache_v2.plist doesn't exist.
+[NSMutableDictionary(GCKAdditions) gck_loadFromCacheWithName:] - Device cache file
file:///Users/martin/Library/Developer/CoreSimulator/Devices/318D2E15-C4B0-47D2-97AF-CD560A6063AE/data/Containers/Data/Application/C117BB98-88DA-4586-B119-0683DAD82FEB/Library/Caches/gck_device_cache_v3.plist doesn't exist
I can't seem to figure out why I have these errors. But it seems that the deviceScanner never even finds my receiver device.
My viewController code is:
class ChromeCastViewController: UIViewController, GCKDeviceScannerListener, GCKDeviceManagerDelegate, GCKMediaControlChannelDelegate{
fileprivate let kCancelTitle = "Cancel"
fileprivate let kDisconnectTitle:String! = "Disconnect"
// Publicly available receiver to demonstrate sending messages - replace this with your
// own custom app ID.
fileprivate let kReceiverAppID = "XXXXXXXXX"
fileprivate lazy var btnImage:UIImage = {
return UIImage(named: "icon-cast-identified.png")!
}()
fileprivate lazy var btnImageselected:UIImage = {
return UIImage(named: "icon-cast-connected.png")!
}()
fileprivate var deviceScanner:GCKDeviceScanner?
fileprivate var deviceManager:GCKDeviceManager?
fileprivate var mediaInformation:GCKMediaInformation?
fileprivate var selectedDevice:GCKDevice?
#IBOutlet weak var googleCastButton: UIBarButtonItem!
#IBOutlet weak var backButton: UIBarButtonItem!
#IBAction func backAction(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
self.startScan()
}
func startScan() {
let filterCriteria = GCKFilterCriteria(forAvailableApplicationWithID: kReceiverAppID)
self.deviceScanner = GCKDeviceScanner(filterCriteria: filterCriteria)
if let deviceScanner = self.deviceScanner {
deviceScanner.add(self)
deviceScanner.startScan()
print("scanning started")
deviceScanner.passiveScan = true
}
}
// MARK: GCKDeviceScannerListener
func deviceDidComeOnline(_ device: GCKDevice) {
print("deviceDidComeOnline")
print("Device found: \(device.friendlyName)");
self.updateButtonStates()
}
func deviceDidGoOffline(_ device: GCKDevice) {
print("deviceDidGoOffline()")
print("Device went away: \(device.friendlyName)");
self.updateButtonStates()
}
func deviceDidChange(_ device: GCKDevice) {
print("deviceDidChange()");
}
func updateButtonStates() {
print("updateButton")
if (deviceScanner!.devices.count > 0) {
// Show the Cast button.
navigationItem.rightBarButtonItems = [googleCastButton!]
if (deviceManager != nil && deviceManager?.connectionState == GCKConnectionState.connected) {
// Show the Cast button in the enabled state.
googleCastButton!.tintColor = UIColor.blue
} else {
// Show the Cast button in the disabled state.
googleCastButton!.tintColor = UIColor.gray
}
} else{
// Don't show Cast button.
navigationItem.rightBarButtonItems = []
}
}
}
Thank you in advance for any help or tips you can give me.
Best regards
UPDATE:
I have modified my code base to follow google v3 guidelines.
I now instantiate a GCKCastContext in AppDelegate in order to use google widgets.
But It seems that GCKCastContext or functionalities associated with the singleton are never called after initialising it. I have tried to add the GCKDiscoveryManagerListener to my AppDelegate to see if was detecting my ChromeCast device.
The code for my AppDelegate is :
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GCKLoggerDelegate,GCKDiscoveryManagerListener {
var window: UIWindow?
fileprivate let kReceiverAppID = "XXXXXXXXXX"
var discoveryManager: GCKDiscoveryManager
override init(){
let options:GCKCastOptions = GCKCastOptions(receiverApplicationID: kReceiverAppID)
GCKCastContext.setSharedInstanceWith(options)
self.discoveryManager = GCKCastContext.sharedInstance().discoveryManager
super.init()
self.discoveryManager.add(self)
self.discoveryManager.passiveScan = true
self.discoveryManager.startDiscovery()
GCKLogger.sharedInstance().delegate = self
}
.....
func log(fromFunction function: UnsafePointer<Int8>, message: String) {
let functionName = String(cString: function)
print(functionName + " - " + message);
}
func didUpdateDeviceList() {
print("didUpdateDeviceList with \(discoveryManager.deviceCount) devices")
(0..<discoveryManager.deviceCount).forEach { index in
print(index, discoveryManager.device(at: index))
}
}
func didStartDiscoveryForDeviceCategory(deviceCategory: String) {
print("GCKDiscoveryManagerListener: \(deviceCategory)")
print("FOUND: \(self.discoveryManager.hasDiscoveredDevices)")
}
func willUpdateDeviceList(){
print("will update device was called")
}
}
The functions didUpdateDeviceList, didStartDiscoveryForDeviceCategory
, willUpdateDeviceList are never called, meaning no Chrome device is ever found making the widgets unusable.
Thank again for any help

UISwitch switched off by user but is still programatically on

I am experimenting with UISwitches on xcode using Swift. I have it to where there is a handful of Company names and their corresponding on and off switches. From top to bottom, if the user switches for example, "Google" Switch on, then they are able to view the chosen companies facebook and twitter. If they choose another company, once they turn that switch on for example "Samsung" , the Google switch turns off then the user is able to see Samsungs facebook and twitter page.
My issue is... from top to bottom, it works fine. Im able to see both pages from Google to samsung. But if I go from samsung back to Google, the google switch is on but it still shows samsung's pages. But, if I switch the saumsung switch on,off then go to google, it works fine.
My assumption is that while the switch visually looks off once another switch is turned on from bottom to top, it is still programmatically on
I'm wondering if people have had a similar issue. If not, I hope this helps those in the future if they find themselves in a similar situation
Here is my main view Controller
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var GoogleSwitch: UISwitch!
#IBOutlet weak var SamsungSwitch: UISwitch!
#IBOutlet weak var FordSwitch: UISwitch!
#IBOutlet weak var ToyotaSwitch: UISwitch!
#IBOutlet weak var SquareEnixSwitch: UISwitch!
#IBOutlet weak var ABCBankSwitch: UISwitch!
var Google: Bool = false
var Samsung: Bool = false
var Ford: Bool = false
var Toyota:Bool = false
var SquareEnix :Bool = false
var ABCBank :Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// ABCBankSwitch.addTarget(self, action: Selector("switchIsChanged:"), forControlEvents: UIControlEvents.ValueChanged)
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func ActiveGoogle(sender: UISwitch) {
if sender.on == true
{
GoogleSwitch.on = true
SamsungSwitch.on = false
ToyotaSwitch.on = false
SquareEnixSwitch.on = false
ABCBankSwitch.on = false
Google = true
// GoogleSwitch.on = true
}
else
{ Google = false
sender.on = false
}
// sender.on = false
}
#IBAction func ActiveSamsung(sender: UISwitch) {
if sender.on == true
{
GoogleSwitch.on = false
SamsungSwitch.on = true
FordSwitch.on = false
ToyotaSwitch.on = false
SquareEnixSwitch.on = false
ABCBankSwitch.on = false
Samsung = true
}
else
{sender.on = false
Samsung = false}
}
#IBAction func ActiveFord(sender: UISwitch) {
if sender.on == true
{Ford = true
GoogleSwitch.on = false
SamsungSwitch.on = false
FordSwitch.on = true
ToyotaSwitch.on = false
SquareEnixSwitch.on = false
ABCBankSwitch.on = false
}
else
{ if sender.on == false {
Ford = false}
}
}
#IBAction func ActiveToyota(sender: UISwitch) {
if sender.on == true
{ Toyota = true
GoogleSwitch.on = false
SamsungSwitch.on = false
FordSwitch.on = false
ToyotaSwitch.on = true
SquareEnixSwitch.on = false
ABCBankSwitch.on = false
}
else
{ Toyota = false}
}
#IBAction func ActiveEnix(sender: UISwitch) {
if sender.on == true
{ SquareEnix = true
GoogleSwitch.on = false
SamsungSwitch.on = false
FordSwitch.on = false
ToyotaSwitch.on = false
//SquareEnixSwitch.on = true
ABCBankSwitch.on = false
}
else
{ SquareEnix = false
// sender.on = false
}
}
#IBAction func ActiveABC(sender: UISwitch) {
if sender.on == true
{ ABCBank = true
ABCBankSwitch.setOn(true, animated: true)
GoogleSwitch.on = false
SamsungSwitch.on = false
FordSwitch.on = false
ToyotaSwitch.on = false
SquareEnixSwitch.on = false
}
}
else
{
ABCBankSwitch.setOn(false, animated: true)
ABCBank = false
// sender.on = false
}
}
}
here is my FacebookViewController. (not putting the twitter on as it is almost identical to the facebook one)
import UIKit
class FacebookViewController: UIViewController{
var webAddress: String = ""
#IBOutlet weak var CompanyFB: UIWebView!
override func viewWillAppear(animated: Bool) {
let myCompany: ViewController = self.tabBarController!.viewControllers![0] as! ViewController
let showGoogle: Bool = myCompany.Google
let showSamsung: Bool = myCompany.Samsung
let showFord: Bool = myCompany.Ford
let showToyota: Bool = myCompany.Toyota
let showEnix: Bool = myCompany.SquareEnix
let showBank:Bool = myCompany.ABCBank
if showGoogle {
webAddress = "https://www.facebook.com/Google/"
//myCompany.GoogleSwitch.on = false had this to test. either way is still doesnt work
// CompanyFB.loadRequest(NSURLRequest(URL: NSURL(string: webAddress)!))
}
if showSamsung {
webAddress = "https://www.facebook.com/SamsungUSA"
// myCompany.SamsungSwitch.on = false
// CompanyFB.loadRequest(NSURLRequest(URL: NSURL(string: webAddress)!))
}
if showFord {
webAddress = "https://www.facebook.com/ford/"
// CompanyFB.loadRequest(NSURLRequest(URL: NSURL(string: webAddress)!))
}
if showToyota {
webAddress = "https://www.facebook.com/toyota"
// CompanyFB.loadRequest(NSURLRequest(URL: NSURL(string: webAddress)!))
}
if showEnix {
webAddress = "https://www.facebook.com/SquareEnix"
}
if showBank {
webAddress = "https://www.facebook.com/ABC-Bank-132047286857188/"
}
CompanyFB.loadRequest(NSURLRequest(URL: NSURL(string: webAddress)!))
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Your problem is that you aren't keeping your separate booleans in sync with your switches. Consider this code:
#IBAction func ActiveGoogle(sender: UISwitch) {
if sender.on == true {
GoogleSwitch.on = true
SamsungSwitch.on = false
ToyotaSwitch.on = false
SquareEnixSwitch.on = false
ABCBankSwitch.on = false
Google = true
}
else {
Google = false
sender.on = false
}
}
If I change the "Google" switch then then Google variable will be set to true/false accordingly. This code also turns off all of the other switches, but it doesn't change the boolean associated variables. So, if after selecting Google I then select "Samsung", the Google switch will be turned off but the Google variable will still be true.
You could use a computed property rather than the separate booleans:
var Google: Bool {
return self.GoogleSwitch.on
}
and so on.
Also, by convention variables and functions should start with a lower case letter, so it should be var google and func activateGoogle
There are a few things going on here, some unrelated to your problem, but I'll comment on them as well so you can improve as a coder.
1) Convention for Swift dictates that you name variables and methods starting with a lower case letter. So GoogleSwitch should be googleSwitch etc. Also, try and be descriptive with the names. In your FacebookViewController you have a variable myCompany for the ViewController class. First, the class should be called something like MyCompanyViewController so you know which view controller it is. And then the myCompany variable could be called myCompanyViewController. CompanyFB should be something like companyFBWebView.
2) You can really simply your code a lot here. Use a single IBAction for all of the switches. A common problem (which may be an issue here), is when you copy and paste controls in Interface Builder, it sometimes will copy the actions assigned as well, and then you add another, so it calls two methods each time it's switched. By using a single method, you save a tonne of duplicated code and avoid this problem.
Your entire first ViewController class can be condensed to this (then connect all of your switches to the same IBOutlet switchToggled::
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var googleSwitch: UISwitch!
#IBOutlet weak var samsungSwitch: UISwitch!
#IBOutlet weak var fordSwitch: UISwitch!
#IBOutlet weak var toyotaSwitch: UISwitch!
#IBOutlet weak var squareEnixSwitch: UISwitch!
#IBOutlet weak var abcBankSwitch: UISwitch!
#IBAction func switchToggled(sender: UISwitch) {
// Remember the state of the triggered switch because we're about to turn it off
let newState: Bool = sender.on
googleSwitch.on = false
samsungSwitch.on = false
fordSwitch.on = false
toyotaSwitch.on = false
squareEnixSwitch.on = false
abcBankSwitch.on = false
// Restore the state of the switch
sender.on = newState
}
}
3) Avoid using duplicate variables for the same thing, the Bool for Google etc should be the same as the switch, so just use the switch.on value. Edit: (or as Paulw11 mentioned, use a calculated property).
4) When using an if to test a variable state, or multiple variable states as you're doing in the FacebookViewController class, when only one possible case should happen, use else if instead of multiple if's. In your case, you're triggering two or more cases at once (due to your other bugs). If you used else if clauses, you'd only ever trigger one and you likely would have narrowed down your other bug earlier.
if showGoogle {
webAddress = "https://www.facebook.com/Google/"
}
else if showSamsung {
webAddress = "https://www.facebook.com/SamsungUSA/"
}
...
5) Expanding on #4 you could change this to make use of Swift's powerful enum features.
import UIKit
class FacebookViewController: UIViewController {
enum Company: String {
case Google = "https://www.facebook.com/Google/"
case Samsung = "https://www.facebook.com/SamsungUSA/"
case Ford = "https://www.facebook.com/ford/"
case Toyota = "https://www.facebook.com/toyota/"
case SquareEnix = "https://www.facebook.com/SquareEnix/"
case ABCBank = "https://www.facebook.com/ABC-Bank-132047286857188/"
}
var company: Company = .Google // Default to Google
#IBOutlet weak var companyFBWebView: UIWebView!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let myCompanyViewController: ViewController = self.tabBarController!.viewControllers![0] as! ViewController
if myCompanyViewController.googleSwitch.on {
company = .Google
}
else if myCompanyViewController.samsungSwitch.on {
company = .Samsung
}
else if myCompanyViewController.fordSwitch.on {
company = .Ford
}
else if myCompanyViewController.toyotaSwitch.on {
company = .Toyota
}
else if myCompanyViewController.squareEnixSwitch.on {
company = .SquareEnix
}
else if myCompanyViewController.abcBankSwitch.on {
company = .ABCBank
}
if let url = NSURLRequest(URL: NSURL(string: company.rawValue)) {
companyFBWebView.loadRequest(url)
}
}
}
6) In Swift, avoid using ! like the plague. Ideally you'll only use it for IBOutlet's and other cases you know for certain a value exists. The line let myCompanyViewController: ViewController = self.tabBarController!.viewControllers![0] as! ViewController is likely to crash in some cases. Possibly due to timing issues, or other changes you may make to the code down the road. Safer to check the options with if let and handle the error gracefully.
7) I just noticed another issue as I was proofreading. Anytime* you override a method from a super class, make sure you also call the super's version. So your viewWillAppear(animated:) etc calls need to call the super version or you can get really weird, hard to track down bugs.
I put a star on Anytime in #7 because there are some cases where you intentionally don't want to call the super method, but those are rare and you'll know it when the time comes.
I'm assuming your IBActions are hooked up to the ValueChanged event, and you expect them to fire in two instances: when you change the switch by touching it; or when you change the value of the on property.
If so, the problem is that the event does not fire in the second case, only the first. Therefore, once Google is set to true, the only way it gets set back to false is by touching the switch to off.
Since your model only wants one switch to be on, you could instead have one variable containing the switch that should be on. When you touch a switch, just update that one variable and redisplay all of them. Using an enum for this variable would be helpful. Along the lines of:
enum Switches {
case None
case Google
case Samsung
}
var onSwitch = Switches.None
Then code in each IBAction along the lines of:
#IBAction func ActiveGoogle(sender: UISwitch) {
if sender.on == true {
onSwitch = Switches.Google
updateSwitches()
}
}
And updateSwitches looks like:
func updateSwitches() {
GoogleSwitch.on = (onSwitch == Switches.Google)
SamsungSwitch.on = (onSwitch == Switches.Samsung)
}

Resources