I have two ViewControllers, assume one is named MainVC and the other one is named GetCameraRollImageViewController. In the second one, there is a UIScrollView which holds a UIImageView, since I'm using segue to show the second view controller, I've to use its delegate in the MainVC:
class ViewController: UIViewController, GetTextDelegate, GetCameraRollImageDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
//
//other codes
//
#IBAction func setNewImageFromCameraRoll(segue:UIStoryboardSegue) {
if let newImageVC = segue.sourceViewController as? GetCameraRollImageViewController{
var scale:CGFloat = 1.0/newImageVC.scrollView.zoomScale;
var visibleRect:CGRect!
visibleRect.origin.x = newImageVC.scrollView.contentOffset.x * scale;
visibleRect.origin.y = newImageVC.scrollView.contentOffset.y * scale;
visibleRect.size.width = newImageVC.scrollView.bounds.size.width * scale;
visibleRect.size.height = newImageVC.scrollView.bounds.size.height * scale;
var cr:CGImageRef = CGImageCreateWithImageInRect(newImageVC.inputImage.image?.CGImage,visibleRect)
var cropped:UIImage = UIImage(CGImage: cr)!
imageView.image = cropped
}
}
}
The GetCameraRollImageViewController code:
import UIKit
protocol GetCameraRollImageDelegate{
//
}
class GetCameraRollImageViewController: UIViewController, UIScrollViewDelegate {
var delegate:GetCameraRollImageDelegate? = nil
var inputImageDelegate:UIImage!
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var inputImage:UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.scrollView.minimumZoomScale = 0.5
self.scrollView.maximumZoomScale = 6.0
self.scrollView.contentSize = self.inputImage.frame.size;
self.scrollView.delegate = self
inputImage.image = inputImageDelegate
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return self.inputImage
}
}
But when I trigger setNewImageFromCameraRoll() it crashes the app with this error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Note: Bothe scrollView and inputImage are bounded, and I'm not reading them in the viewDidLoad(), when they are empty.
I'm not 100% but I believe your problem is that the scrollView has not yet been created. Try passing the image to the new view controller and doing your resizing in the viewDidLoad of GetCameraRollImageViewController.
Your code has a number of problems.
First of all, why do you have an IBAction that takes a segue as a parameter?
Usually what you do is invoke a segue through whatever method, and then implement the preformSegue method. That method gets called whenever a segue is triggered in your view controller.
In performSegue you can get a pointer to the destination view controller and pass parameters to it.
It isn't clear to me how your setNewImageFromCameraRoll method is being called, or how it gets a segue object. It also doesn't make sense to me that you're using the source view controller from the segue. I suspect the method doesn't work as you expected.
The next problem has to do with what you're trying to do with the view controller you get from the segue. You're trying to manipulate it's views directly. Don't do that. You should treat a view controller's views as private. Instead you should add properties to your view controller that expose the information you need, and reference those properties.
Messing around with another view controller's views violates the principle of encapsulation. It also frequently fails because the other view controller's views don't get created until it's displayed on-screen.
According to me you haven't connected delegate of V2 class to self in MainVC
"newImageVC?.delegate=self"
[Updated based on comments]
The line
var visibleRect:CGRect!
assigns visibleRect to nil and thus the subsequent line of
visibleRect.origin.x = newImageVC.scrollView.contentOffset.x * scale;
will then dereference visibleRect, find nil and produce the exception. You need to allocate a CGRect and assign it to visibleRect.
[Original Answer]
Is visibleRect bound? It doesn't look to be. If that is bound, then you'll notice other uses of !... You need to bind your outlets (typically in InterfaceBuilder):
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var inputImage:UIImageView!
Perhaps scrollView is not bound and thus, because it is implicitly unwrapped (the ! operator), when you reference it as nil your program will fatal.
Use:
var visibleRect =
CGRectMake (newImageVC.scrollView.contentOffset.x * scale,
newImageVC.scrollView.contentOffset.y * scale,
newImageVC.scrollView.bounds.size.width * scale,
newImageVC.scrollView.bounds.size.height * scale)
Related
I've created a custom UIView with multiple IBOutlets including a UIImageView, and three UILabels. Despite setting the image value in the awakeFromNib() function, I'm still getting a nil value for the outlet when attempting to set it. The UIView was constructed using a custom xib file.
I put the set operation in awakeFromNib() so as to assure the outlets had been initialized prior to setting them, yet this failed to help. I'm not sure if it's an issue with the xib file as I've only ever used custom xibs when making a custom table cell, so perhaps the issue is rooted there?
class VotingCard: UIView {
#IBOutlet weak var proPicImg: UIImageView!
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var hometownLabel: UILabel!
#IBOutlet weak var ratingLabel: UILabel!
var proPic = UIImage()
var name = ""
var hometown = ""
var rating = 0.0
init(pic: UIImage, rush: Rush) {
proPic = pic
name = rush.fullName
hometown = rush.homeTown
rating = rush.compositeRating
super.init(frame: CGRect(x: 0, y: 0, width: 250, height: 300))
}
override func awakeFromNib() {
proPicImg.image = proPic
nameLabel.text = name
hometownLabel.text = hometown
ratingLabel.text = "\(rating)"
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
I should view a simple card with the profile image and respective data visible, yet instead it crashes.
Your problem is either
1-The imageView isn't connected to Ib , and the fix is to connect it
Or
2- You create an object of the view with init(pic: UIImage, rush: Rush) { and that for sure won't load the UI with view , hence all outlets are nil , and the fix to add this method
class func getInstance(_ pic: UIImage, rush: Rush) -> VotingCard {
let v = Bundle.main.loadNibNamed("VotingCard", owner: self, options: nil)!.first as! VotingCard
v.proPic = pic
v.name = rush.fullName
v.hometown = rush.homeTown
v.rating = rush.compositeRating
return v
}
Call
let v = VotingCard.getInstance(<#image#>,rush:<#rush#>)
v.frame = // set some frame
// ready for use
EDIT
NIB-based development is sort-of deprecated:
For iOS developers, using storyboards is the recommended way to design
user interfaces.
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/NibFile.html
But, looks like you're missing this step:
At runtime, you load a nib file using the method loadNibNamed:owner:
or a variant thereof. The File’s Owner is a placeholder in the nib
file for the object that you pass as the owner parameter of that
method. Whatever connections you establish to and from the File’s
Owner in the nib file in Interface Builder are reestablished when you
load the file at runtime.
See as an example:
https://medium.com/#umairhassanbaig/ios-swift-creating-a-custom-view-with-xib-ace878cd41c5
If you want to do it the recommended way with Storyboard (apparently not, but this is for general audience):
https://developer.apple.com/library/archive/referencelibrary/GettingStarted/DevelopiOSAppsSwift/WorkWithViewControllers.html
viewDidLoad()—Called when the view controller’s content view (the top
of its view hierarchy) is created and loaded from a storyboard. The
view controller’s outlets are guaranteed to have valid values by the
time this method is called. Use this method to perform any additional
setup required by your view controller.
So with the UIViewController method, put your code in viewDidLoad() instead.
Keep in mind this is always called, so this will overwrite the work of your custom init() function. Perhaps this is what you were running into.
I would refactor it to avoid ugly code. Create the Rush object always (declare as Optional as stored property), but initialize it in awakeFromNib() for the XIB case, and copy the input object in the custom-init case.
Then viewDidLoad() has no conditionals.
I have a custom view created by subclassing UIView, and it has two UITextView subviews which are instantiated inside its init method. Let's say for clarifying things, that I have a superview A containing two UITextViews B and C.
On the other hand, I have the UIViewController VC that manages the superview A, and its responsible of its control. This means that it has the method that controls what happens when the user changes text on C.
When someone has to control a UITextView, he usually does something like
textView.delegate = self
I want to do the same, but C is not visible from VC. VC just uses A, so I have
a.delegate = self
and then I have come with the idea of doing this inside the implementation of A:
weak var delegate: UITextViewDelegate? {
get {
return c.delegate
}
set {
c.delegate = newValue
}
}
But this is giving me an error, saying that it's unwrapping an optional value whose actual value is nil.
So, what's the correct way of controlling UITextView C (that is inside UIView A) from VC, which only has an instance of A.
It is most probably that c maybe nil. To solve this, you need to set the delegate after c is initialised.
Alternatively, create your own delegate - ADelegate! This way you can name your own delegate methods with more meaningful names!
protocol ADelegate : class {
func cDidChange()
}
In A:
weak var delegate: ADelegate?
Now A should implement UITextViewDelegate, set c.delegate to self, and relay the methods to self.delegate.
If you have added the UITextField's in the Xcode Interface Builder, you can create outlets for the textfields in your ViewController (even when the textfields are sub-sub-children of the view, that the viewcontroller controls).
Thus continuing with your naming, you add the following outlets to your ViewController:
#IBOutlet weak var B: UITextField!
#IBOutlet weak var C: UITextField!
and in your ViewController's viewDidLoad() add:
B.delegate = self
C.delegate = self
If you created the UITextField's programmatically you can set their tag value to e.g. 1 and 2(0 is most likely already used) and then get the view from your ViewController like this:
let B = self.view.viewWithTag(1) as? UITextField
let C = self.view.viewWithTag(2) as? UITextField
Of course, make sure that the ViewController extends UITextFieldDelegate, that is:
class ViewController: UIViewController, UITextFieldDelegate {...}
I'm dealing with some deallocation issue and perhaps strong or circular referencing that can't figure out. I have three UIViews instantiating like below:
There is one main ViewController which I have added a UIView inside it in storyboard and the UIView has a weak outlet inside the class like:
class ViewController : UIViewController {
//MARK: - outlets
#IBOutlet weak var firstView: FirstUiview!
}
second UIView is added as a subview to the first view programmatically like:
class FirstUiview : UIView {
//creating an instance of secondUiView
lazy var mySecondView: SecondViewClass = {
let dv = SecondViewClass()
dv.backgroundColor = UIColor.red
return dv
}()
//sometime later by clicking on a button
self.addSubview(mySecondView)
//a button will be tapped to remove mySecondView;
//later will be called at some point upon tapping:
func removingSecondViewByTapping() {
if mySecondView.isDescendant(of: self) {
mySecondView.removeFromSuperview()
}
}
}
Now the SecondViewClass is :
class SecondViewClass : UIView {
//in this class I create bunch of uiview objects like below:
lazy var aView : UIView = {
let hl = UIView()
hl.tag = 0
hl.backgroundColor = UIColor.lightGray
return hl
}()
self.addSubview(aView) //... this goes on and I add other similar views the same way.
//creating an instance of thirdView
var let thirdView = UIView()
self.addSubview(thirdView)
}
Now if user taps the button to remove mySecondView and then add it again at some other time (still in the same ViewController) I expect all the subviews of mySecondView to have been released and gone but they are all there. I would appreciate it a lot if someone can point it to me where am I keeping a strong reference or if there is a circular referencing issue? or perhaps something else?
You have two strong references to your views, your custom property and the view hierarchy reference established when you call addSubview. When you remove the view from the view hierarchy, your class, itself, still has its strong reference to it.
You could solve this by making your reference optional, and when you call removeFromSuperview, also manually set your reference to nil. Or, perhaps easier, you might resolve this by using weak references, letting the view hierarchy maintain the strong references for you. And because your custom property is weak, when you remove it from the view hierarchy (thus eliminating the only strong reference to it), your weak reference will automatically become nil:
class FirstView: UIView {
weak var secondView: SecondView? // note the `weak` reference, which is obviously an optional
//sometime later by clicking on a button
func doSomething() {
let subview = SecondView()
subview.backgroundColor = .red
self.addSubview(subview)
secondView = subview
}
// a button will be tapped to remove secondView;
// later will be called at some point upon tapping ...
func removingSecondViewByTapping() {
secondView?.removeFromSuperview()
}
}
I am new to iOS development so forgive me if I'm missing something obvious. I have a view controller that contains a subview in which I've created a numpad, and for the time being I want to give the numpad view its own UIView subclass because I want to do a few different things with it. Right now the numpad is just creating a string from the keys that get pressed, and I've set up a delegate to pass that string anywhere else I want to use it (though I've also tried accessing the raw input directly in the view controller with let a = subview(); label.text = a.rawInput).
Whenever I try to set the text of the UILabel in the view controller to the subview's raw input, whether by delegation or directly, the UILabel is found to be nil and throws the error in the title.
Things I've tried:
Setting the text inside a viewDidLoad override, and outside of it
Setting a variable (testInput) inside the view controller to adopt the subview's raw input and setting the label text to that (I've confirmed that the variable inside the view controller gets properly set, so no delegation issues)
Using didSet on the testInput variable both to set label text to testInput and to try calling viewDidLoad and set the label text in there (printing testInput inside this didSet does print the right string, FWIW)
Deleting and relinking the IBOutlet for my label
Strong and weak storage for the IBOutlet variable
Trying to do the same thing in another subview within the view controller, in case for some reason it was the view controller's own fault
Searching everywhere for a solution that works
I'm stumped. Here is my relevant numpad code:
import UIKit
protocol NumpadDelegate {
func updateInput(input: String)
}
class Numpad: UIView {
// MARK: UI outlets
#IBOutlet weak var decButton: UIButton!
// MARK: Properties
var rawInput: String = ""
var visibleInput: String = ""
var calcInput: String = ""
var operandReady = 1
var percentWatcher = 0
var delegate: NumpadDelegate? = BudgetViewController()
// MARK: Functions
func handleRawInput(str: String) {
rawInput += str
print("numpad input is \(rawInput)")
delegate?.updateInput(rawInput)
}
And here is the view controller code:
import UIKit
class BudgetViewController: UIViewController, NumpadDelegate {
// MARK: Properties
//#IBOutlet weak var transactionValueField: UITextField!
#IBOutlet weak var remainingCashForIntervalLabel: UILabel!
#IBOutlet weak var intervalDenoterLabel: UILabel!
#IBOutlet weak var currencyDenoterLabel: UILabel!
#IBOutlet weak var mainDisplayView: TransactionType!
#IBOutlet weak var inactiveInputView: InactiveInput!
#IBOutlet weak var numpadView: Numpad!
#IBOutlet weak var rawInputLabel: UILabel!
var remainingCashForInterval = 40
let display = TransactionType()
var testInput = "" {
didSet {
viewDidLoad()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// let numpad = Numpad()
// numpad.delegate = self
// print("\(numpad.delegate)")
self.rawInputLabel.text = testInput
}
func updateInput(input: String) {
print("view controller now has \(input)")
display.mainInput = input
testInput = input
}
As a side note, in case you noticed that my protocol isn't a class type, for some reason adding : class to it and declaring my delegate as a weak variable prevents the delegation from working. Any suggestions there?
You assigned the delegate like so:
var delegate: NumpadDelegate? = BudgetViewController()
That doesn't reference the view controller whose scene was presented, but rather a new blank one. And that's why when you used weak, why it was deallocated (because that orphaned instance of the view controller has no strong references to it).
You should define the protocol to be a class protocol again, and define delegate to be:
weak var delegate: NumpadDelegate?
And then, in the view controller's viewDidLoad, uncomment the line that sets that delegate:
numpadView.delegate = self
But, do not uncomment the line that says numpad = Numpad(); that is incorrect as that creates yet another Numpad instance. But you do want to set the delegate of the existing Numpad, though.
Both of these issues (namely, getting a reference to the view controller that is to be the delegate of the Numpad view; and getting a reference to the Numpad view that the storyboard presented) suggest some misunderstanding about the the process of presenting a storyboard scene.
The process is basically as follows:
the view controller is instantiated, using whatever class you specified as the base class for that scene;
its root view, as well as all of the subviews on that scene will be instantiated for you;
the storyboard will hook up the IBOutlet references in the scene's base class to the outlets you created; and
the view controller's viewDidLoad is called.
That's an oversimplification, but that's the basic process.
But the key is that all of these view controllers and views that are referenced on the storyboard scene are created for you. You don't want to try to create any of these yourself (and the presence of the () at the end of BudgetViewController() or Numpad() says "create a new instance of x", which is not what we want to do here).
So, when we need to get a reference to the view controller so that we can programmatically specify the delegate for one of the views, you can do this in viewDidLoad, at which point self references the view controller that the storyboard instantiated for us. We don't want to instantiate a new one. Likewise, when you want to reference the Numpad that the storyboard instantiated for us (in order to hook up its delegate), you use the IBOutlet you hooked up in Interface Builder, rather than programmatically instantiate a new Numpad with Numpad().
All of the searches I've done focus on passing data between view controllers. That's not really what I'm trying to do. I have a ViewController that has multiple Views in it. The ViewController has a slider which works fine:
var throttleSetting = Float()
#IBAction func changeThrottleSetting(sender: UISlider)
{
throttleSetting = sender.value
}
Then, in one of the Views contained in that same ViewController, I have a basic line that (for now) sets an initial value which is used later in the DrawRect portion of the code:
var RPMPointerAngle: CGFloat {
var angle: CGFloat = 2.0
return angle
}
What I want to do is have the slider's value from the ViewController be passed to the View contained in the ViewController to allow the drawRect to be dynamic.
Thanks for your help!
EDIT: Sorry, when I created this answer I was having ViewControllers in mind. A much easier way would be to create a method in SomeView and talk directly to it.
Example:
class MainViewController: UIViewController {
var view1: SomeView!
var view2: SomeView!
override func viewDidLoad() {
super.viewDidLoad()
// Create the views here
view1 = SomeView()
view2 = SomeView()
view.addSubview(view1)
view.addSubview(view2)
}
#IBAction func someAction(sender: UIButton) {
view1.changeString("blabla")
}
}
class SomeView: UIView {
var someString: String?
func changeString(someText: String) {
someString = someText
}
}
Delegate:
First you create a protocol:
protocol NameOfDelegate: class { // ": class" isn't mandatory, but it is when you want to set the delegate property to weak
func someFunction() // this function has to be implemented in your MainViewController so it can access the properties and other methods in there
}
In your Views you have to add:
class SomeView: UIView, NameOfDelegate {
// your code
func someFunction() {
// change your slider settings
}
}
And the last step, you'll have to add a property of the delegate, so you can "talk" to it. Personally I imagine this property to be a gate of some sort, between the two classes so they can talk to each other.
class MainViewController: UIViewController {
weak var delegate: NameOfDelegate?
#IBAction func button(sender: UIButton) {
if delegate != nil {
let someString = delegate.someFunction()
}
}
}
I used a button here just to show how you could use the delegate. Just replace it with your slider to change the properties of your Views
EDIT: One thing I forgot to mention is, you'll somehow need to assign SomeView as the delegate. But like I said, I don't know how you're creating the views etc so I can't help you with that.
In the MVC model views can't communicate directly with each other.
There is always a view controller who manages the views. The views are just like the controllers minions.
All communication goes via a view controller.
If you want to react to some view changing, you can setup an IBAction. In the method you can then change your other view to which you might have an IBOutlet.
So in your example you might have an IBAction for the slider changing it's value (as in your original question) from which you could set some public properties on the view you would like to change. If necessary you could also call setNeedsDisplay() on the target view to make it redraw itself.