Here is the situation. I have a protocol, and extension of it.
protocol CustomViewAddable {
var aView: UIView { get }
var bView: UIView { get }
func setupCustomView()
}
extension CustomViewAddable where Self: UIViewController {
var aView: UIView {
let _aView = UIView()
_aView.frame = self.view.bounds
_aView.backgroundColor = .grey
// this is for me to observe how many times this aView init.
print("aView: \(_aView)")
return _aView
}
var bView: UIView {
let _bView = UIView(frame: CGRect(x: 30, y: 30, width: 30, height: 30))
_bView.backgroundColor = .yellow
return _bView
}
func setupCustomView() {
view.addSubview(aView);
aView.addSubview(bView);
}
}
And I make a ViewController to conform this protocol then I add this custom 'aView' to my ViewController's view.
class MyVC: UIViewController, CustomViewAddable {
override func viewDidLoad() {
super.viewDidLoad()
setupCustomView()
}
}
I run it. In my console log it prints twice of init and I trying to do something in my custom 'aView' and it failed. (The code I paste above that I simplified so that it'll be very easy to show my intension)
Could anybody to explain why or make a fix to it that I'll be very appreciated.
Because your var aView: UIView is computed variable not store variable,
So every time you call aView, it will create a new UIView,
You can use Associated Objects in NSObject here is some tutorials:
swift-objc-runtime
associated-objects
Hope this may help.
Basically in the way you implemented the setupCustomView method nothing should work because as mentioned in another response you're using a computed property, so this implies that every time you access the property it's created again.
You don't need to use associated-objects or something like that to achieve what you want, you only need to keep the reference of the aView at the beginning avoiding calling it again, in this way:
func setupCustomView() {
let tView = aView // only is computed once
view.addSubview(tView)
tView.addSubview(bView)
}
I hope this help you.
Related
I've got an NSButton in a View Controller that, when clicked, should call a method in an instance of another class (I have that instance in the View Controller). However, the action method is never called.
My code is below (it's short and simple). Please can somebody explain why this is?
View Controller class with the button:
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let b:NSButton = NSButton(frame: NSRect(x: 150, y: 200, width: 30, height: 30))
self.view.addSubview(b)
let g = Global()
b.target = g
b.action = #selector(g.s)
}
}
Class called 'Global' that I create an instance of, that the button should then call a method within:
class Global:NSObject {
override init() {
super.init()
}
#objc dynamic func s() {
Swift.print("S ran")
}
}
Thanks
Update: For easy reproduction, I've created a GitHub repo showing the issue in its simplest form here.
The problem is that by the time you click the button, target has been set to nil. This is because g is stored as a local variable and target is a weak property, so after viewDidLoad is finished, g is released and the target becomes nil. So, by the time you click the button, there is no object on which to call the action.
You need to store a strong reference to the target somewhere. One way would be to store it as an instance variable on your view controller:
class ViewController: NSViewController {
let g = Global()
override func viewDidLoad() {
super.viewDidLoad()
let b:NSButton = NSButton(frame: NSRect(x: 150, y: 200, width: 30, height: 30))
self.view.addSubview(b)
b.target = g
b.action = #selector(g.s)
}
}
I'm sorry for my incompetence, but I'm new to Cocoa, Swift, and object-oriented programming, in general. My primary sources have been Cocoa Programming for OS X (5th ed.), and Apple's jargon- and Objective-C-riddled Developer pages. But I'm here because I haven't seen (or didn't realize that I saw) anything that speaks to this problem.
I want to change the contents of one IB-created custom view, LeftView, by mouseEntered/-Exited actions in another IB-created custom view, RightView. Both are in the same window. I created a toy program to try to figure things out, but to no avail.
Here's the class definition for RightView (which is supposed to change LeftView):
import Cocoa
class RightView: NSView {
override func drawRect(dirtyRect: NSRect) {
// Nothing here, for now.
}
override func viewDidMoveToWindow() {
window?.acceptsMouseMovedEvents = true
let options: NSTrackingAreaOptions =
[.MouseEnteredAndExited, .ActiveAlways, .InVisibleRect]
let trackingArea = NSTrackingArea(rect: NSRect(),
options: options,
owner: self,
userInfo: nil)
addTrackingArea(trackingArea)
}
override func mouseEntered(theEvent: NSEvent) {
Swift.print("Mouse entered!")
LeftView().showStuff(true)
}
override func mouseExited(theEvent: NSEvent) {
Swift.print("Mouse exited!")
LeftView().showStuff(false)
}
}
And here's the class definition for LeftView (which is supposed to be changed by RightView):
import Cocoa
class LeftView: NSView {
var value: Bool = false {
didSet {
needsDisplay = true
Swift.print("didSet happened and needsDisplay was \(needsDisplay)")
}
}
override func mouseUp(theEvent: NSEvent) {
showStuff(true)
}
override func drawRect(dirtyRect: NSRect) {
let backgroundColor = NSColor.blackColor()
backgroundColor.set()
NSBezierPath.fillRect(bounds)
Swift.print("drawRect was called when needsDisplay was \(needsDisplay)")
switch value {
case true: NSColor.greenColor().set()
case false: NSColor.redColor().set()
}
NSBezierPath.fillRect(NSRect(x: 40, y: 40,
width: bounds.width - 80, height: bounds.height - 80))
}
func showStuff(showing: Bool) {
Swift.print("Trying to show? \(showing)")
value = showing
}
}
I'm sure I'm missing something "completely obvious," but I'm a little dense. If you could tell me how to fix the code/xib files, I would very much appreciate it. If you could explain things like when talking to a child, I would be even more appreciative. When I take over the world (I'm not incompetent at everything), I will remember your kindness.
I came up with a workaround that is much simpler than my prior strategy. Instead of having mouseEntered/-Exited actions in one custom view try to control what is displayed in another custom view, I simply put the mouseEntered/-Exited code into the view that I wanted to control, and then I altered the location of rect: in NSTrackingArea.
Before moving code over by for that approach, I had tried changing the owner: in NSTrackingArea to LeftView() and just moving over the mouseEntered/-Exited code. That generated lots of scary error messages (naive newbie talking here), so I gave up on it. It would be nice to know, though, how one can correctly assign an owner other than self.
In any case, any further thoughts or insights would be appreciated.
Below is just a test of delegation.
What I did was, 1) draw a rectangle, 2) set this rectangle's width of line with a delegate, 3) Hope the storyboard could update its display.
There are two questions:
The first is: If I use "testView.widthdelegate = ViewController()" rather than "testView.widthdelegate = self" , the "var widthValue: CGFloat? = widthdelegate?.trueWidth" will be nil, but it should be 50, what's different between "self" and "ViewController()"?
The second is: I still want to update the result of draw in storyboard, where you can see I did a SetNeedDisplay() but no use at all, how could I do it?
View
import UIKit
protocol widthDelegate: class {
var trueWidth: CGFloat { get }
}
#IBDesignable
class TestView: UIView {
weak var widthdelegate: widthDelegate?
override func drawRect(rect: CGRect) {
var widthValue: CGFloat? = widthdelegate?.trueWidth ?? 1.0
rectangle(widthRefer: widthValue!)
println("width in TestView is \(widthdelegate?.trueWidth)" )
}
func rectangle(#widthRefer: CGFloat) -> UIBezierPath{
var rect = UIBezierPath(rect: CGRect(x: bounds.width/2-50, y: bounds.height/2-50, width: 100, height: 100))
rect.lineWidth = widthRefer
rect.stroke()
return rect
}
}
Controller
import UIKit
class ViewController: UIViewController,widthDelegate {
var trueWidth: CGFloat = 50
#IBOutlet var testView: TestView!{
didSet{ //after the storyboard loaded.
// testView.widthdelegate = ViewController()
testView.widthdelegate = self
testView.setNeedsDisplay()
}
}
}
Answer 1:
self is the actual instance of the class the code is in (correct solution)
ViewController() creates a brand new instance of ViewController which is not identical with the instance created in IB (wrong solution)
Answer 2:
Never implement didSet for an IBOutlet because it's never called during initialization. Better use viewDidLoad() for settings
Some other notes:
Please consider the naming convention that class, protocol and enum names start with a capital letter.
The class constraint in the protocol declaration is not needed
I have a very general question about the Initialization in Swift.
Unlike in Objective C it's now possible to call the init() directly at the declaration outside of my functions:
e.g.
class ViewController: UIViewController {
let myView: UIView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
myView.frame = getFrame()
myView.backgroundColor = UIColor.redColor()
self.view.addSubview(myView)
}
func getFrame() -> CGRect {
return CGRectMake(0, 0, 100, 100)
}
}
In Objective C I would have done the initialization in my function.
But what if I want to call an Initializer with parameters which are not set yet?
e.g.
I want to init with a frame which is being calculated in a func()
class ViewController: UIViewController {
//THIS IS NOT WOKRING
let myView: UIView = UIView(frame: getFrame())
override func viewDidLoad() {
super.viewDidLoad()
myView.backgroundColor = UIColor.redColor()
self.view.addSubview(myView)
}
func getFrame() -> CGRect {
return CGRectMake(0, 0, 100, 100)
}
}
I don't wanna do my initializations at two different places in the Code. Is there any general pattern for the initializations?
So your options for initialisation in swift are numerous. With your current example you cannot use the method getFrame() yet because you do not yet have a reference to self as the ViewController has not get been initialised. Instead you could use:
let myView: UIView = UIView(frame: CGRectMake(0, 0, 100, 100))
As this does not require the reference to self. Alternatively you could lazy instantiation which will get run after self is available (this can only be used with var not let:
lazy var myView: UIView = {
return UIView(frame:self.getFrame())
}()
To answer your question, when using UIKit class where you often don't have control over their instantiation you can keep doing it the same was as you were in objective c and use implicitly unwrapped optionals (to prevent you having to use a ! or ? every time you instantiate a variable, e.g.:
var myView: UIView!
override func viewDidLoad(){
super.viewDidLoad();
myView = UIView(frame:getFrame())
}
This however does not work with let constants, as they need to be assigned either immediately or in the constructor of the object. Hope this helps.
I am trying to create a custom activity indicator like one of these. There seems to be many approaches to animating the image, so I'm trying to figure out the best approach.
I found this tutorial with Photoshop + Aftereffects animation loop, but as the comment points out, it seems overly complex (and I don't own After Effects).
tldr: how can I take my existing image and animate it as a activity indicator (using either rotating/spinning animation or looping through an animation)
It's been a while since you posted this question, so I'm not sure if you've found the answer you're looking for.
You are right there are many ways to do what you are asking, and I don't think there's a "right way". I would make a UIView object which have a start and a stop method as the only public methods. I would also make it a singleton, so it's a shared object and not possible to put multiple instances on a UIViewController (imagine the mess it could make).
You can add a dataSource protocol which returns a UIViewController so the custom activity indicator could add itself to it's parent.
In the start method I would do whatever animations is needed (transform, rotate, GIF, etc.).
Here's an example:
import UIKit
protocol CustomActivityIndicatorDataSource {
func activityIndicatorParent() -> UIViewController
}
class CustomActivityIndicator: UIView {
private var activityIndicatorImageView: UIImageView
var dataSource: CustomActivityIndicatorDataSource? {
didSet {
self.setupView()
}
}
// Singleton
statice let sharedInstance = CustomActivityIndicator()
// MARK:- Initialiser
private init() {
var frame = CGRectMake(0,0,200,200)
self.activityIndicatorImageView = UIImageView(frame: frame)
self.activityIndicatorImageView.image = UIImage(named: "myImage")
super.init(frame: frame)
self.addSubview(self.activityIndicatorImageView)
self.hidden = true
}
internal required init?(coder aDecoder: NSCoder) {
self.activityIndicatorImageView = UIImageView(frame: CGRectMake(0, 0, 200, 200))
self.activityIndicatorImageView = UIImage(named: "myImage")
super.init(coder: aDecoder)
}
// MARK:- Helper methods
private func setupView() {
if self.dataSource != nil {
self.removeFromSuperview()
self.dataSource!.activityIndicatorParent().addSubview(self)
self.center = self.dataSource!.activityIndicatorParent().center
}
}
// MARK:- Animation methods
/**
Set active to true, and starts the animation
*/
func startAnimation() {
// A custom class which does thread handling. But you can use the dispatch methods.
ThreadController.performBlockOnMainQueue{
self.active = true
self.myAnimation
self.hidden = false
}
}
/**
This will set the 'active' boolean to false.
Remeber to remove the view from the superview manually
*/
func stopAnimation() {
ThreadController.performBlockOnMainQueue{
self.active = false
self.hidden = true
}
}
}
Mind that this is not fully tested and may need some tweaks to work 100%, but this is how I basically would handle it.