In a UIViewController (rolePageController) I configure another UIViewController (drawerController) and pass it 2 UIViews from the role page that will be part of the drawerController's configuration. As soon as the drawerController tries to access the IBOutlet views from the rolePageController, it crashes with EXC_BAD_ACCESS (code=EXC_I386_GPFLT).
In the 1st VC (rolePageController), here are the IBOutlets:
#IBOutlet var rolePageDrawerView: UIView!
#IBOutlet var rolePageContentView: UIView!
In rolePageController.viewDidLoad() I make a call to the drawerController.configureDrawer(...):
override func viewDidLoad() {
super.viewDidLoad()
//other stuff happens here
let drawerController = UIStoryboard(name: "StoryboardName", bundle: nil).instantiateViewController(withIdentifier: "drawerController") as! DrawerViewController
drawerController.configureDrawer(drawerContainerView: self.rolePageDrawerView, overlaidView: self.rolePageContentView)
//other stuff here
}
The DrawerViewController protocol is defined as:
protocol DrawerViewController where Self: UIViewController {
func configureDrawer(drawerContainerView: UIView, overlaidView: UIView)
}
Here is the code for the configureDrawer(...) func:
private var drawerParentView: UIView!
private var overlaidByDrawerView: UIView!
func configureDrawer(drawerContainerView: UIView, overlaidView: UIView) {
self.drawerParentView = drawerContainerView
self.overlaidByDrawerView = overlaidView
}
Noticed in the debugger that the drawerController instance that is called does not match the self instance that receives the call. Here is the address of the instance that will be called:
Here is the address of the instance when I step into the call:
The address of drawerController before the call is not the address of self when I step into the call. That should never happen.
I have created a simplified project that reproduces the crash at https://github.com/ksoftllc/DynamicStackBufferOverflow.
Solution
Solution turned out to be to remove the where clause from the DrawerViewController protocol.
protocol DrawerViewController where Self: UIViewController {
func configureDrawer(drawerContainerView: UIView, overlaidView: UIView)
}
Found the offending code, but I don't know why this would cause the errors I was seeing. The drawerController conforms to DrawerViewController protocol, defined as:
protocol DrawerViewController where Self: UIViewController {
func configureDrawer(drawerContainerView: UIView, overlaidView: UIView)
}
When I remove the Where condition, it no longer crashes.
protocol DrawerViewController {
func configureDrawer(drawerContainerView: UIView, overlaidView: UIView)
}
The where clause was not actually necessary for correct function of the program, so I will proceed without it.
UPDATE
I filed a bug with swift.org and received a response. Adding a where clause to a protocol is not supported in Swift 4.2, but will be supported in Swift 5.0. In addition, #J Doe posted below a way to accomplish this with an update to Xcode toolkit.
dynamic-stack-buffer-overflow doesn't have anything to do with recursion. It means an alloca buffer was overrun. Check the asan runtime source code.
Suppose the stack is laid out so that you have an alloca buffer followed by an object pointer—maybe even one of the object pointers passed as an argument.
Suppose the alloca buffer gets overrun. In an asan build, this can trigger a dynamic-stack-buffer-overflow error. But in a non-asan build, it just writes over the bytes of that object pointer. Suppose it writes bytes that form an address that's not mapped in your process's page table.
If the program tries to read that object pointer and store it elsewhere (say, in an instance variable), it has to increment the reference count of the object. But that means dereferencing the pointer—and the pointer points to an unmapped address. Perhaps that's leading to a general protection fault, which Mach calls an EXC_I386_GPFLT.
It would be helpful if you posted the stack trace of the asan dynamic-stack-buffer-overflow error, and the disassembly of the code leading up to the error.
It really looks like Swift compiler bug. I simplified your code for clarification:
func foo(_ wow: TestProtocol) {
wow.foo()
}
protocol TestProtocol where Self: NSObject {
func foo()
}
class TestClass: NSObject, TestProtocol {
func foo() {
print("Much wow")
}
}
foo(TestClass())
You can report this as bug. To resolve this issue I propose you not use where statement or pass object with it's type func foo(_ wow: TestClass {.
To fix your issue, run it on the development trunk toolchain snapshot. You can download it here:
https://swift.org/download/
Go to Snapshots -> Trunk Development (master) XCode (so not Swift 5.0) and download the snapshot as of 15 December (I got the one from 30 November, but I am sure 15 December will work as well.)
After you installed the toolchain, in XCode go to: File -> Preferences -> Components and select the newest toolchain. It now runs without any crash.
Also, the where Self: UIViewController can be shorten to :UIViewcontroller (This only works on the newest toolchains):
protocol DrawerViewController: UIViewController {
func configureDrawer(drawerContainerView: UIView, overlaidView: UIView)
}
Had a similar issue today. For some reason, it was only occurring on the latest macOS.
The reason was that I hadn't specified a correct subclass for one of my IBOutlet in Storyboard.
The funny thing is that exc_bad_access occurred on another IBOutlet, albeit a subview of the one which wasn't appropriately specified. So, all in all, someone may find it helpful to check that all subclasses of IBOutlets are correctly specified in Storyboard :)
Move this Function Call from viewDidLoad to viewWillAppear
drawerController.configureDrawer(drawerContainerView: self.rolePageDrawerView, overlaidView: self.rolePageContentView)
Related
Example: I have a SpeechSynthesizer class that needs to update something in my UIView when it’s done uttering a piece of text. Since the SpeechSynthesizer class conforms to protocol AVSpeechSynthesizerDelegate, it is the one that receives the didFinish signal when the uttering has been completed. The idea here is to keep the ViewController from having too many delegate methods and a long list of protocols to conform to. The workaround I found was to have the ViewController passed in as a SpeechSynthesizer initialization parameter. This way I get to access the ViewController connected to the UIView I want to update from inside the SpeechSynthesizer class. The thing I don’t like about it is that it looks kind of ugly to have the ViewController passed in as a parameter to every single class that needs to use it. So I wonder, which other way I could accomplish this.
I suppose another way to ask the question is: How can I make the function
private func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance)
return something to a ViewController since it's not "called" by it?
I added a reply on Quora. Copying it here:
After doing some research and testing on code of my own here are 2 solutions to this problem.
Solution 1: The Delegate Pattern:
Create a custom delegate protocol in the ViewController
protocol ViewControllerDelegate:class {
func getViewLayer() -> CALayer
}
The ViewController must conform to this newly created protocol and therefore implement all the functions defined by it, so somewhere in the class ViewController you add:
public func getViewLayer() -> CALayer {
return self.view.layer
}
Then on my custom class, ReadTextMachine, I added a variable of the ViewControllerDelegate type
private weak var viewControllerDelegate: ViewControllerDelegate?
The variable must be weak and protocol must be of type class in order to solve a “retain cycle” problem (since both the custom class and the ViewController will point to each other)
You’ll notice now that the function call inside the ViewController is already “callable” from the custom class, so in my ReadTextMachine I added:
let viewLayer = self.viewControllerDelegate?.getViewLayer()
self.cameraPreview = CameraPreview(session: self.camera.getSession(), container: viewLayer!)
self.cameraPreview?.addPreview()
In the above case, my CameraPreview (yes, a 3rd class in this example) simply adds a camera preview layer on the UIView. For that it needed access to the main View’s layer.
The above code still doesn’t work because our original viewController’s instance hasn’t been passed as reference anywhere in our code. For that we add the following function in ReadTextMachine:
public func setViewControllerDelegate(viewController: ViewController) { // call this from the ViewController so that ViewController can be accessed from here.
self.viewControllerDelegate = viewController
}
The above piece of code will have to be called from the ViewController, after we instantiate our custom class (ReadTextMachine), so that the viewControllerDelegate inside it points to the ViewController. So in our ViewController.swift:
operatingMode = ReadTextMachine()
operatingMode.setViewControllerDelegate(viewController: self)
Another example and explanation can be found in this video from LetsBuildThatApp. I derived my solution mostly from it.
My current app in development applying the above solution can be found here: agu3rra/World-Aloud
Solution 2: Notifications and Observers pattern
This one is a bit easier to understand and follow. The general idea is to have your custom class broadcast a message which triggers a function call on your ViewController since it has an observer setup, waiting to hear that message.
So to give an example, in the context I used it, I have a CameraCapture class which uses AVFoundation to capture a photo. The capture photo trigger cannot immediately return an image, since iOS has a set of steps to execute before actually generating an image. I wanted my ReadTextMachine to resume an activity after CameraCapture had a photo available. (To apply this in the context of the CustomClass triggers ViewController event is basically the same, since both are actual classes in an iOS app as well).
So the 1st thing I did was create a broadcast function since I would use it in many places in my app. I simply placed it in a Utilities.swift file in the Xcode project.
public func broadcastNotification(name: String) {
let notification = Notification.Name(rawValue: name)
NotificationCenter.default.post(name: notification, object: nil)
}
The above function takes a string, which must be a unique notification identifier, and broadcasts it thru NotificationCenter.
In my CameraCapture class, I added a static constant to reference the unique identifier of the message:
static let NOTIFY_PHOTO_CAPTURED = "agu3rra.worldAloud.photo.captured"
For those who know AVFoundation, a photo is available when event didFinishProcessingPhoto gets executed, so at the end of that I added:
broadcastNotification(name: CameraCapture.NOTIFY_PHOTO_CAPTURED)
The above is a call to my previously defined utility function.
For my ReadTextMachine class to be able to catch that notification, I added the following on its init() and deinit routines:
override init() {
super.init()
// Setup event observers
let notification1 = Notification.Name(rawValue: CameraCapture.NOTIFY_PHOTO_CAPTURED)
NotificationCenter.default.addObserver(self,
selector: #selector(self.processingDoneTakingPhoto),
name: notification1,
object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self) // cleanup observer once instance no longer exists
}
Removing the observer is important at deinit so that when your object is deallocated from memory, the observer isn’t left lingering around. The above configured observer triggers a function call inside ReadTextMachine:
#IBAction private func processingDoneTakingPhoto() {
// does my stuff
}
That’s it! Again, the entire Xcode project can be downloaded from my project’s Git repository: agu3rra/World-Aloud
Hope this can be of use to others.
Cheers!
I have an UIViewController with 4 UIButtons. A user can tap any of those UIButtons and an UIView pops up. I want to add an didAppear() and didDisappear() function on the classes which are holding the UIViews depending on the users action. How can I call didDisappear() without the use of an enum, for example:
func didDisappear(view: EnumViews){
switch view{
case view0: myClassWithView0.didDisappear()
case view1: myClassWithView1.didDisappear()
case view2: myClassWithView2.didDisappear()
case view3: myClassWithView3.didDisappear()
}
}
Now I get 4 times duplicate data. I know that function exists for my class with a UIView, but how to call it? I made a protocol:
protocol ViewProtocol{
func didAppear()
func didDisappear()
}
I made the classes which are holding the UIView's conform to that protocol. However I do not know how to use it, when I create the class I get the error:
'myClassWithUIView' cannot be constructed because it has no accessible
initializers
The classes are all in an array and I can identify which UIView needs to pop up from the sender.tag. Ideally, I want to have something like this:
#IBAction func bringNewUIView(_ sender: UIButton) {
let newView = myArrayOfClassesWithUIView[sender.tag]
newView.didAppear()
}
You've got many things going on here. I'll start with the easy one.
'myClassWithUIView' cannot be constructed because it has no accessible initializers
This just means you don't have an initializer for your class. So inside your myClassWithUIView implementation you need to have init. I can't really help you with building the init because I don't know how that class is structured, but I will assume this is something you know how to do anyway.
Your #IBAction seems fine. Once you have an array of your classes that seems like it should work. Edit your post if that is not the case.
Finally, for your didDisappear question, you can do something like this:
func didDisappear(view: EnumViews) {
//Check to see if this view conforms to your ViewProtocol (that's not a good name, btw)
if let myClass = view as? ViewProtocol {
//Since it does conform to ViewProtocol you can call didDisappear on it
myClass.didDisappear()
}
}
Alternatively, if you already know that the didDisappear function is always passing in a view that conforms to ViewProtocol why not just change the argument and make that easier?
func didDisappear(view: ViewProtocol) {
view.didDisappear()
}
I am using external SDK(by including their xcode project in my project).
The SDk was working properly in objective-c, but when I switched to swift, I am getting following problem.
Whenever I implementing delegate method where parameter is of type protocol, xcode suddenly gives error to object declaration of that Class which declared globally i.e. not in any function. If I comment that particular delegate method I will not get any error and it compile/executes successfully.
Please check the following swift code followed by my # comments
//CustomView is subclass of UIView
var customview : CustomView = CustomView() // #1 error as : Use of undeclared type CustomView
#IBAction func showCustomView(sender: AnyObject)
{
// CustomView configurations
}
#pragma CustomView Delegates
func CustomViewShown(view: AnyObject!) /// #2 delegate work properly
{
}
func CustomView(view: AnyObject!, didFailWithError error: NSError!)
// #3 if I keep this method uncommented it gives error to #1 line
// if I commented this method all other works fine without error.
{
}
Surprising thing is all the above delegate and SDK works fine for objective-C but not for swift.
On the basis of my little research, I am concluding that,
We can not use the Class name and method name same in swift, i.e. in my case its CustomView. If I am using CustomView for declaring object, I can not use it as method name.
so someone please verify that, I am correct or not ? and what is solution for this issue.
It's essentially a name conflicting problem.
Inside of your class declaration, CustomView is a method name, but not a class name. So, basically, your assumption is correct.
But, you have a workaround.
Let's suppose CustomView is declared in the SDK. And that is a framework named SomeSDK. Then you can reference the CustomView like this:
import SomeSDK
class ViewController: UIViewController {
var customview: SomeSDK.CustomView = SomeSDK.CustomView()
func CustomView(view: AnyObject!, didFailWithError error: NSError!) {
}
}
If you don't want to prefix SomeSDK. everywhere, you can typealias it:
import SomeSDK
typealias SDKCustomView = CustomView // you can use `CustomView` here because there is no conflicting name.
class ViewController: UIViewController {
var customview: SDKCustomView = SDKCustomView()
func CustomView(view: AnyObject!, didFailWithError error: NSError!) {
}
}
I may be wrong, but it seems in swift you can also explicitly call the init function.
Instead of calling:
var customview : CustomView = CustomView()
you can call:
var customview : CustomView = CustomView.init()
This works in my Playground, let me know how it works out for you. This would allow you to use your function named as it is.
I am trying to send a double value from a UIView (which is loaded from a XIB) to a ViewController using a delegate
Here is my protocol, it is just sending a double to the main ViewController on a button click.
protocol HoursWorkedDelegate{
func sendHoursWorked(hoursWorked: Double);
}
class HoursWorkedView: UIView{
var delegate: HoursWorkedDelegate?;
#IBAction func calculateHoursWorked(sender: AnyObject){
// Do some calculations for the hoursWorked value
// Give value
delegate!.sendHoursWorked(hoursWorked);
}
}
// This class now wants that Double value
class ViewController: UIViewController, HoursWorkedDelegate{
// Conform to protocol
func sendHoursWorked(hoursWorked: Double){
// Lets say we just want to assign this value to a textField
hoursWorkedTextField.text = NSString(format: "%.4f", hoursWorked);
}
}
The error message I get is Thread 1: EXC_BAD_INSTRUCTION(code = EXC_I386_INVOP, subcode=0x0)
Any help would be much appreciated, Thank You!
As a start, change the exclamation point in this snippet to a question mark:
delegate!.sendHoursWorked(hoursWorked);
This is what's likely causing the crash, as you are force-unwrapping the optional delegate property. A question mark means we'll only call sendHoursWorked() on the delegate if the delegate exists.
That fix will now probably mean that your program is no longer crashing, but you still don't get the desired results, because sendHoursWorked() is never called. We have to tell our HoursWorkedView object who is delegating it.
Somewhere in your code, you might have something like this:
let hoursWorkedView = HoursWorkedView()
self.view.addSubview(hoursWorkedView)
It's right here where we should be setting the delegate:
let hoursWorkedView = HoursWorkedView()
hoursWorkedView.delegate = self
self.view.addSubview(hoursWorkedView)
Though if it's me, I probably add a constructor to HoursWorkedView that accepts the delegate property:
init(delegate: HoursWorkedDelegate) {
super.init()
self.delegate = delegate
}
And now we can just do this:
let hoursWorkedView = HoursWorkedView(delegate: self)
self.view.addSubview(hoursWorkedView)
I think you're getting your view and your viewcontroller mixed up: a ViewController controls things; a view just displays them. The viewController tells the view what to display.
So, you want to connect your button to the viewController -- not the view. And you don't need a custom view class or a delegate.
Set it up like this:
create a textField and a button
create an outlet for the textField
put calculateHoursWorked directly in your viewController
create an action to connect the button to calculateHoursWorked
in calculateHoursWorked, set self.textField.text to the result of the calculation (where "textField" is whatever you named your outlet)
You wouldn't use a delegate in this context because the viewController knows everything the view does. The delegate pattern is for cases where one object has no visibility into another.
EDIT:
That being said, the bug here is that the delegate isn't actually being set anywhere.
Swift Optionals (the ! and ?) help prevent cases like this. If you explicitly unwrap an optional using !, you have to make sure it's always defined. In this case, since delegate is defined as optional (?) you have to check it:
#IBAction func calculateHoursWorked(sender: AnyObject){
// Do some calculations for the hoursWorked value
// Give value
if let currentDelegate = self.delegate {
currentDelegate.sendHoursWorked(hoursWorked)
}
}
I am new to Swift/iOS, so please bear with me:
I am trying to access a function in one class from another class, and update an UIImage name.
Within my viewcontroller class I have
class Documents: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var UpdateImage: UIImageView
override func viewDidLoad() {
super.viewDidLoad()
UpdateImage()
}
func UpdateImage() {
UpdateImage.image = UIImage(named: "NewImage")
}
}
Everything works, the Image gets updated to "NewImage"
Question: I can access the UpdateImage func from another class, but why is it generating an error when trying to change the image in the Documents class?
class GetChanges {
var success = { operation:AFHTTPRequestOperation!, response:AnyObject!) -> Void in
var MakeChange = Documents()
MakeChange.UpdateImage()
}
}
This generates an error on the "UpdateImage.image = UIImage(named: "NewImage")" in the Documents Class; "fatal error: unexpectedly found nil while unwrapping an Optional value"
When you call it within the class itself, it is operating on itself and it has already been created from a nib/storyboard. This means that UpdateImage exists.
When you call the method from another class, when you call this line:
var MakeChange = Documents()
You are creating a new instance of Documents. This is not initialized through the nib/storyboard, and thus it never populated the IBOutlet value UpdateImage. Because this value doesn't exist, it unexpectedly finds nil and throws an error.
You need to somehow retain a reference to the instance of Documents you're trying to display. I'd need more information to tell you how to do that.
Also, because you mentioned that you're new, I'd like to point out a few issues I notice with your code that is making it very difficult to read.
Capitalized names are reserved for Types variable names should (almost) never begin with a capital letter.
Variable names should reflect the object they represent. UpdateImage sounds like it is an image. It would be better to name this updateImageView
Functions should be lowercase as well. It is strange to see capitalization this way and makes the code a bit uncomfortable to read.
Good luck!
Read about View Contoller's lifecycle, it's very important knowledge for iOS developer.
As Logan said:
You are creating a new instance of Documents. This is not initialized through the nib/storyboard, and thus it never populated the IBOutlet value UpdateImage
This means that after call init for ViewController (i.e. Documents()) nib isn't loaded. You can use outlets of viewController in another code only after viewDidLoad stage. Apple docs:
The nib file you specify is not loaded right away. It is loaded the first time the view controller's view is accessed. If you want to perform additional initialization after the nib file is loaded, override the viewDidLoad() method and perform your tasks there.
You can remove MakeChange.UpdateImage(), because it will be called in viewDidLoad. Or, if you want pass specific image name to view controller:
class Documents: UIViewController, UITableViewDataSource,
UITableViewDelegate {
#IBOutlet var UpdateImage: UIImageView
var imageName: String?
override func viewDidLoad() {
super.viewDidLoad()
updateImageView()
}
func updateImageView() {
if let imageName = imageName {
UpdateImage.image = UIImage(named: imageName)
}
}
}
After that, you can use
let documentsViewController = Documents
documentsViewController.imageName = "newImage"
When you load documentsViewController, newImage will be presented