iOS app crashes on Segue - ios

I'm trying to build an app for school but I keep getting error messages. It's probably a really obvious mistake I made.
Basically I am trying to build a view that displays a UIWebView and changes to a 2nd view if a segment controller switch is pressed.
My Code is:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myWebView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "http://google.de")
myWebView.loadRequest(URLRequest(url: url!))
}
#IBAction func Heute(_ sender: UISegmentedControl) {
performSegue(withIdentifier: "Switch", sender: self)
}
}
//Vertretung2
class Vertretung2: UIViewController {
#IBOutlet weak var UIWebView1: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "http://google.de")
UIWebView1.loadRequest(URLRequest(url: url!))
}
#IBAction func Morgen(_ sender: UISegmentedControl) {
performSegue(withIdentifier: "Switch", sender: self)
}
}
My app keeps crashing when I switch from 1st view to 2nd view.

Is that UIWebView1 in Vertretung2 nil when viewDidLoad() is called on Vertretung2?
Ok, Sorry. I just read your crash log above, and it looks like you maybe forgot to set the Custom Class name in the storyboard for the viewController? Check that here.
The Class text entry field should have your custom class in it.

Related

Web View crashes

Hello and thanks for reading this,
I'm building a simple app with 2-3 tabs (view controllers), and the first one is some kind of converter (miles to km), it's fine, and the second viewcontroller has to have a "webview" element.
So, when I build a separate app with only ONE view controller, (with "import WebKit") it works fine:
super.viewDidLoad()
webview.load(URLRequest(url: URL(string: "https://www.google.com")!))
BUT when I copy this to my 2-tabbed app code under the line #super.viewDidLoad()#, it crashes saying "Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value".
I'm really new with this all, so I don't know how to fix this. And I didn't find solutions to similar problem on this forum. Thank you in advance!
p.s. how am I creating web view - visually add it. I see the tip to create it by coding, I will try, thank you
p.p.s. Im adding here my code:
import UIKit
import SafariServices
import WebKit
extension String {
func toDouble() -> Double? {
return NumberFormatter().number(from: self)?.doubleValue
} }
class ViewController: UIViewController {
#IBOutlet var mileField: UITextField!
#IBOutlet var mileResult: UILabel!
#IBOutlet var meters: UILabel!
#IBOutlet var cmeters: UILabel!
#IBAction func mileButton(_ sender: UIButton) {
let mile: Double = mileField.text?.toDouble() ?? 0
let km = mile * 1.6
let m = km * 1000
let cm = m * 100
meters.text = String(m)+" m"
mileResult.text = String(km)+" km"
cmeters.text = String(cm)+" cm"
}
#IBOutlet var vw: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
vw.load(URLRequest(url: URL(string: "google.com")!)) // this is where I got Fatal error blah blah blah
}
#IBAction func bTapped() {
let vc = SFSafariViewController(url: URL(string: "https://www.apple.com")!)
present(vc, animated: true) // this works fine but I need to use webview element
}
}
a little screenshot
Declare your two view controllers. Also please assign this class names to your view controller in storyboard.
https://i.stack.imgur.com/BDoY6.png
class ViewController1: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
class ViewController2: UIViewController {
#IBOutlet var webview: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webview.load(URLRequest(url: URL(string: "https://www.google.com")!))
}
}

Swift delegate doesn't get called

I'm using PanelKit for an iOS app that I'm developing right now.
I have set up a panel, which is a ViewController, and have set up a button in that ViewController. When pressed, I want a picture to be displayed in another ViewController (the main VC that's instantiated when the app is launched). I'm using a delegate for this. However, my delegate function never gets executed. Here's my code:
Delegate:
protocol IMGPickerDelegate: class {
func didFinishPicking(_ imageData: UIImage)
}
Panel ViewController:
class IMGLibraryViewController: UIViewController, PanelContentDelegate {
weak var delegate: IMGPickerDelegate?
#IBAction func fireButton(_ sender: Any) {
let imageSample = UIImage(named: "sample")!
didPickImage(imageDData: imageSample)
}
func didPickImage(imageDData: UIImage) {
delegate?.didFinishPicking(imageDData)
}
Main ViewController:
class ImageDisplayViewController: UIViewController, IMGPickerDelegate {
#IBOutlet weak var imageDisplayViewOutlet: UIImageView!
var imgLibrary = IMGLibraryViewController()
func didFinishPicking(_ imageData: UIImage) {
imageDisplayViewOutlet.image = imageData
}
override func viewDidLoad() {
super.viewDidLoad()
imgLibrary.delegate = self
}

Passing an object with data from one view to another view in Swift using Segue

I am trying to pass an object with data from one view to another view using Swift4. However, I get this problem when trying to print.
Here is the message that I get:
2018-03-21 21:47:03.542578-0400 Sudoku[64427:1070575] <UIView:
0x7fd28bc0b4c0; frame = (0 0; 414 736); autoresize = W+H; layer =
<CALayer: 0x604000038500>>'s window is not equal to
<Sudoku.setupSudoku: 0x7fd28bf256c0>'s view's window!
Here is the picture of my View. Identifier for segue: setupScreen
Here is the code in setPlayer:
class setPlayer: UIViewController {
var singleUser:playerInfor=playerInfor()
var randomGame:Int = -1
var playerName:String=""
#IBOutlet weak var txtPlayerName: UITextField!
var sizeOfSudoku:Int = 9 //for project 1, let take one size of sudoku
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor(patternImage: UIImage(named: "Communication-Network-Vector-Illustration.jpg")!)
self.txtPlayerName.layer.borderWidth=2.0
self.txtPlayerName.layer.borderColor=UIColor.blue.cgColor
// Do any additional setup after loading the view.
}
func randomNumber(){
self.randomGame=Int(arc4random_uniform(3)+1)
}
func initialize(){
//if(size)
if txtPlayerName.text?.isEmpty ?? true {
self.playerName="Player1"
} else {
self.playerName=self.txtPlayerName.text!
}
self.singleUser=playerInfor(playerName:self.playerName,size:self.sizeOfSudoku,time:0, randomSudoku:self.randomGame)
//print (self.singleUser.size)
}
#IBAction func startButton(_ sender: Any) {
initialize()
performSegue(withIdentifier: "setupScreen", sender: self.singleUser)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "setupScreen"{
if let destination=segue.destination as?setupSudoku{
destination.objectPlayer=sender as?playerInfor
print(singleUser.playerName)
}
}
}
#IBAction func size9(_ sender: Any) {
self.sizeOfSudoku=9
}
#IBAction func Size6(_ sender: Any) {
self.sizeOfSudoku=9
}
Here is playerInfor
import Foundation
import UIKit
class playerInfor{
var playerName:String=""
var size:Int=0
var time:Int=var randomSudoku:Int=0
init(playerName:String,size:Int,time:Int,randomSudoku:Int) {
self.playerName=playerName
self.size=size
self.time=time
self.randomSudoku=randomSudoku
}
}
Here is setupSudoku
class setupSudoku: UIViewController , UICollectionViewDelegate, UICollectionViewDataSource{
#IBOutlet weak var stackViewButtons: UIStackView!
#IBOutlet weak var myCollectionView: UICollectionView!
var newNumber=0
var objectPlayer:playerInfor!
override func viewDidLoad() {
super.viewDidLoad()
print(self.objectPlayer?.time)
}
}
You have setup your segue incorrectly in your storyboard, and it is attempting to segue twice. To confirm this is the case, select your segue in your storyboard and check if your button highlights, like this:
To resolve, delete this segue and create a new one, by control-dragging from the view controller (not the button) to the destination, like so:
Make sure to reset the new segues identifier back to setupScreen. Then you should be good to go!

Why passing data with delegate fails in Swift 4

This is my protocol
protocol PassDataDelegate {
func passData(data: String)
}
My first controller
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
var delegate: PassDataDelegate?
override func viewDidLoad() {
super.viewDidLoad()
delegate = SecondViewController()
}
#IBAction func sendDataButtonTapped(_ sender: Any) {
delegate?.passData(data: textField.text!)
performSegue(withIdentifier: "Go", sender: nil)
}
}
And second, final one
class SecondViewController: UIViewController, PassDataDelegate {
#IBOutlet weak var myLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
func passData(data: String) {
print("This came from first: \(data). Will change UI.")
myLabel.text = data
}
}
App is crashing on label changing part. It says nil while unwrapping optional. What is wrong here?
SecondViewController() is not the controller designed in the storyboard. It's a brand new instance without connected outlets (which is the reason of the crash). You need the real reference to the SecondViewController instance.
Assuming the SecondViewController instance is the destination view controller of the segue you don't need protocol / delegate, pass the data through the segue
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
#IBAction func sendDataButtonTapped(_ sender: Any) {
performSegue(withIdentifier: "Go", sender: nil)
}
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Go" {
let secondController = segue.destination as! SecondViewController
controller.passedData = textField.text!
}
}
}
class SecondViewController: UIViewController, PassDataDelegate {
#IBOutlet weak var myLabel: UILabel!
var passedData = ""
override func viewDidLoad() {
super.viewDidLoad()
print("This came from first: \(passedData). Will change UI.")
myLabel.text = passedData
}
}
There are several fundamental issues with your code.
I think there might also be some misapprehensions on your side regarding delegation and UIStoryboardSegue mechanism. You should probably read up on that here (Delegation) and here (Segues).
That being said, let me post a solution to your problem with inline comments explaining what's going on.
// Has to be marked as a class protocol (`: class`) so that
// `weak var delegate: PassDataDelegate?` can be weak.
protocol PassDataDelegate: class {
func passData(data: String)
}
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
// Important!
// Make this a `weak` var. In your case, you would fortunately not create a retain cycle
// but there is a big threat of creating those when using delegation patterns with non-weak delegates.
//
// In your case, if you don't make this `weak`, `SecondViewController` would never be deallocated unless you
// cleared this var manually (setting it to `nil`).
//
// Also note that, if you're using `PassDataDelegate` solely for forwarding some data to the next view controller,
// you can dismiss this var entirely. There is no need to have a reference to the second view controller hanging around.
// In fact, as mentioned above, it can be dangerous to do so.
// Additionally, you don't need to make the protocol `: class` then.
private weak var delegate: PassDataDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// It doesn't make any sense to create a `SecondViewController` here.
// The segue mechanism will create a new instance of `SecondViewController`.
// delegate = SecondViewController()
}
#IBAction func sendDataButtonTapped(_ sender: Any) {
// `delegate?` is `nil` here.
// delegate?.passData(data: textField.text!)
performSegue(withIdentifier: "Go", sender: nil)
}
// This is the proper 'hook' for you to forward data or do anything with a destination
// view controller presented using `UIStoryboardSegue`.
// This method will be called by the system following your call to `performSegue`.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// `UITextField.text` can be `nil`, so safeguard for that here.
// If the destination implements our protocol, we can forward data to it.
if let text = textField.text, let delegate = segue.destination as? PassDataDelegate {
// This is optional. You can hang on to the destination view controller here, but
// review the comments above to reason about whether this makes sense or not.
self.delegate = delegate
// We can safely forward the data (text) here.
delegate.passData(data: text)
}
}
}
SecondViewController can stay as is.
Update
Regarding Delegation
The delegation pattern usually describes a back pointer which talks back to an initiating instance. E.g. using UITableViewDataSource, a UITableView talks back to a thing implementing this protocol to get information about its data and so on.You are essentially doing the opposite here by forwarding data to SecondViewController. As mentioned in the comments, this code even breaks, because the implementation of passData in SecondViewController is using outlets not yet initialised.
Now you can do one of three things here:
1
Keep the pattern you are using right now (which is not delegation to be precise) and change SecondViewController to make things work
class SecondViewController: UIViewController, PassDataDelegate {
#IBOutlet weak var myLabel: UILabel!
private var data: String?
override func viewDidLoad() {
super.viewDidLoad()
// It is safe to access `myLabel` in `viewDidLoad`. Outlets have been connected.
if let data = data {
myLabel.text = data
}
}
func passData(data: String) {
self.data = data
// Only access `myLabel` if the view is loaded.
if isViewLoaded {
print("This came from first: \(data). Will change UI.")
myLabel.text = data
}
}
}
This approach is very cumbersome actually, because you need to manoeuvre around the fact that passData may be called at any time. So you don't know if your outlets have been initialised yet, which leads to bloated and repetitive code. Bad.
2
Strip protocols entirely and use a more straightforward approach
class FirstViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
// This is the proper 'hook' for you to forward data or do anything with a destination
// view controller presented using `UIStoryboardSegue`.
// This method will be called by the system following your call to `performSegue`.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// `UITextField.text` can be `nil`, so safeguard for that here.
// If the destination is a `SecondViewController`, we know that is has `public var data: String` and we can forward data to it.
if let text = textField.text, let destination = segue.destination as? SecondViewController {
// We can safely forward the data (text) here.
destination.data = text
}
}
}
class SecondViewController: UIViewController {
#IBOutlet weak var myLabel: UILabel!
// Deliberatly marking this a `public` to make clear that
// you're intented to set this from the 'outside'.
public var data: String? {
didSet {
if isViewLoaded {
myLabel.text = data
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// It is safe to access `myLabel` in `viewDidLoad`. Outlets have been connected.
if let data = data {
myLabel.text = data
}
}
}
Again, there are things we don't like about his approach:
Still repeating code and having to check for isViewLoaded
You specifically wanted to use protocols, we don't do that here
We could work around the repetitive code issue by providing the data in an init of SecondViewController. However, since you're using segues, the storyboard will instantiate the destination view controller for you and you cannot gain control over that. Now you could strip using segues, but this quickly moves far away from your original question and is a totally different code only approach. So this is no good either.
3
Use protocols but apply the delegation pattern correctly.
protocol DataProvider: class {
func provideData() -> String?
}
protocol DataHandler: class {
var providerDelegate: DataProvider? { get set }
}
class FirstViewController: UIViewController, DataProvider {
#IBOutlet weak var textField: UITextField!
func provideData() -> String? {
return textField.text
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// If the destination is a `DataHandler`, we can set yourselves as its provider.
if let destination = segue.destination as? DataHandler {
destination.providerDelegate = self
}
}
}
class SecondViewController: UIViewController, DataHandler {
#IBOutlet weak var myLabel: UILabel!
weak var providerDelegate: DataProvider?
override func viewDidLoad() {
super.viewDidLoad()
if let data = providerDelegate?.provideData() {
// Safe to access `myLabel`, because we are in `viewDidLoad`.
myLabel.text = data
}
}
}
This approach is the most generic. Both parties don't care what exactly the handler and provider are. Note that in a classical delegation pattern, you would probably not have the DataHandler protocol and check for a concrete type (here SecondViewController) in prepareForSegue. However, this approach is more flexible while still having the delegation weaved into it. This approach is also the most robust from the SecondViewController point of view. Instead of having to handle passData at any moment, it can decide itself when to ask its delegate (DataProvider) for the data. This way, SecondViewController can reason about when all of its outlets, etc. are initialised and it is safe to process the data.

YouTubePlayer pod crashes when loading video ID

I am trying to do something very basic, I have an outlet that is assigned with the YouTubePlayerView class and once loading an ID, it crashes.
Why is it crashing?
I checked that the outlet is connected properly and the debugger shows that videoId is defined correctly.
code:
import UIKit
import YouTubePlayer
class InfoViewController: UIViewController {
var videoId = "Bgh9u7x8i4Y"
#IBOutlet var youtubePlayer: YouTubePlayerView!
override func viewDidLoad() {
super.viewDidLoad()
self.youtubePlayer.loadVideoID(videoId)
}
#IBAction func exit(_ sender: UIButton) {
performSegue(withIdentifier: "unwindToMainFromInfo", sender: self)
}
}
Well, I am still not sure what causes the crash, I believe it is something related to the framework, and choosing the type of a UIView (and setting it as a YouTubePlayerView through the identity inspector) added in Storyboard caused a crash when trying to load the videoId.
The work around was to add the player programatically. I used a plain view in storyboard to make it easy to give my player a frame when initializing it. But otherwise programatically works smoothly.
code:
#IBOutlet weak var viewForPlayer: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let frameForPlayer = viewForPlayer.frame
let player = YouTubePlayerView(frame: frameForPlayer)
player.loadVideoID(InstructionsVideoId)
player.play()
self.view.addSubview(player)
}

Resources