Changing the initial value of a var in another module - swift

I am using this module to display an image:
https://github.com/huynguyencong/ImageScrollView/blob/master/Sources/ImageScrollView.swift
I have added it to my project using CocoaPods and I want to change the value of the var "maxScaleFromMinScale". I just cant figure out how.
First I tried to just override the var which is not possible.
I then tried to override the intializers by just copying and overriding the same ones that are overrided by the creator of the module.
override init(frame: CGRect) {
super.init(frame: frame)
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
That does not work, it gives me an error on the top initializer saying that I am not overriding the designated initializer. It feels like I cannot override it since it is not public like the second one. Correct?
Last thing I tried was to make an extension:
extension ImageScrollView {
public func setMaxScale(scale: CGFloat) {
maxScaleFromMinScale = scale
}
}
That does not work either, maxScaleFromMinScale is an unresolved identifier. This also seems to be because it is an internal var and I do not have access to it (another module). Does this mean that my ONLY option is to copy the whole file and modify the source. Was hoping for a minimal and elegant solution.
If this is the case, why can you override the built in UIKit elements but not this one that I downloaded using CocoaPods.

Yes, you cannot override the variable/function because it's internal. That's the point of access modifiers.
Certain UIKit functions are public so you can override them.

Go to the pod file and unlock the file (It prompts to do that on editing), and change
var maxScaleFromMinScale: CGFloat = 3.0
to
public var maxScaleFromMinScale: CGFloat = 3.0

Related

Xcode 13 convenience init compiler issue

Before I updated to Xcode 13 the code below worked fine in another custom UIView created in Xcode 12.
I created this new custom view and the compiler gives the following contradictory errors with reference to the convenience init
Overriding declaration requires an 'override' keyword
'init(frame:)' has already been overridden
Why is Xcode 13 reporting this error in the new custom class but not the identical one created in Xcode 12?
class OptionsPanel: UIView {
override init(frame : CGRect) {
super.init(frame : frame)
}
convenience init(frame: CGRect) {
self.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
That was the problem. In the other custom view I had other parameters in the convenience init convenience init(frame: CGRect, packages: [Purchases.Package], testing: Bool).
I did try other parameters in this one thinking it might be the problem but had all sorts of issues with the pods Xcode indexing forever until I created a new project and iPhone 13 simulators not showing up.... so it got lost in all that fire fighting!
Thank you Sweeper, Duncan C & Bhawin Ranpura.
I have been asked to edit by Community.
The problem as exactly as answer given -
The convenience init cannot be identical to init. ie
override init(frame : CGRect) {
super.init(frame : frame)
}
convenience init(frame: CGRect) {
self.init(frame: frame)
}
Is rejected by the compiler.

Set Accessibility Identifier in UIView Extension

I worked on Mobile Test Automation.Previous, some elements don't have any identifier but i need to import identifiers for testing issues.
So I decide to write an extension to UIView, hereby that code will be affect all codes so I wont need to add one by one.
How can I do ? Should I write on init or awakeFromNib ?
Thanks in advance.
Generally you'll want to have specific accessibility identifiers for elements you want to expose to the accessibility system.
You can set those directly in Storyboards/Interface Builder, or you can set them in your view's initializer when implementing UIs programatically:
class MyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.accessibilityIdentifier = "my-custom-view"
let label = UILabel()
label.accessibilityIdentifier = "my-custom-label"
self.addSubview(label)
}
}

Any way to opt out of autoresizing permanently?

I'm writing nib-less views in which I use autolayout for all my layout logic. I find myself having to turn off autoresizing with every view I instantiate. My code is littered with a lot of these:
view.translatesAutoresizingMaskIntoConstraints
Ideally I'd like to just
extension UIView/NSView {
override var translatesAutoresizingMaskIntoConstraints: Bool = false
}
and get it over with once and for all, but extensions can't override stored properties.
Is there some other simple way to switch off autoresizing for good?
Well just a suggestion since its annoying to always set that to false, just setup a function with all the shared setups for the UIView and call it every time,
its saves time and its kinda less annoying than trying and setting the values each time,
extension UIView {
func notTranslated() {
self.translatesAutoresizingMaskIntoConstraints = false
//Add any additional code.
}
}
//Usage
let view = UIView()
view.notTranslated()
You can't override this constraints properties because the UIView maybe declared in the IB
translatesAutoresizingMaskIntoConstraints according to apple.
By default, the property is set to true for any view you programmatically create. If you add views in Interface Builder, the system automatically sets this property to false.
imagine if you could override that from an extension that would lead to some conflicts if there was other UIView's that's have the opposite value True || false, so in my opinion:
Apple did this to prevent any conflicts with the views constrains, therefore if you don't like to write it every time just wrap it up in a function.
Please if anyone have additional information, don't hesitate to contribute.
UPDATE: I found this cool answer that could also work, check out the code below.
class MyNibless: UIView {
//-----------------------------------------------------------------------------------------------------
//Constructors, Initializers, and UIView lifecycle
//-----------------------------------------------------------------------------------------------------
override init(frame: CGRect) {
super.init(frame: frame)
didLoad()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
didLoad()
}
convenience init() {
self.init(frame: CGRect.zero)
}
func didLoad() {
//Place your initialization code here
//I actually create & place constraints in here, instead of in
//updateConstraints
}
override func layoutSubviews() {
super.layoutSubviews()
//Custom manually positioning layout goes here (auto-layout pass has already run first pass)
}
override func updateConstraints() {
super.updateConstraints()
//Disable this if you are adding constraints manually
//or you're going to have a 'bad time'
//self.translatesAutoresizingMaskIntoConstraints = false
translatesAutoresizingMaskIntoConstraints = false
//Add custom constraint code here
}
}
var nibless: UIView = MyNibless()
//Usage
nibless.updateConstraints()
print(nibless.translatesAutoresizingMaskIntoConstraints) //false
So simply just create MyNibless instance as UIView and this also open big door to customizations too

Implicit Unwrapping and Initializers

I have a subclass of UIView, which instantiates several sublayers. For convenience, I want a direct reference to one in particular, and because it has the same lifetime as the view, it seems reasonable to make it a let constant. The problem is that I want the sublayer to have a similarly immutable reference back to its owner/parent view, so I try to pass this to the layer’s initializer:
class MyView: UIView{
let myLayer: MyLayer!
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
self.myLayer = MyLayer(owner: self) // <- The problem line.
self.layer.addSublayer(self.myLayer)
}
}
class MyLayer: CAShapeLayer {
unowned let owner: MyView
init(owner: MyView) {
self.owner = owner
super.init ()
}
}
If I put the call to the layer initializer before the call to MyView’s super.init, I get the error “'self' used before 'super.init' call.” I take this to mean it’s permissible (preferred I always thought?) to use self in a property initialization, but it doesn’t yet have a value that can be passed as a parameter?
But if I place the line after super.init as shown, I get two errors: “Property 'self.myLayer' not initialized at super.init call” and “Immutable value 'self.myLayer' may only be initialized once.” This puzzles me, as I thought one point of an implicitly unwrapped optional was that it had a valid nil value from its declaration, and that this did not count as the one and only assignment a let statement permits. (It also sounds a trifle contradictory: something wasn’t initialized, but after an attempted initialization, it wound up initialized twice, nevertheless?)
I know I can get around the problem by dropping my obsession with immutables, but is there a way to do this properly? I’m all in favor of Swift’s safety checking, but is there an actual danger here? I’d think the implicit unwrap would signal the compiler that the programmer is aware of a possible issue and is looking out for consequences.
I thought one point of an implicitly unwrapped optional was that it had a valid nil value from its declaration, and that this did not count as the one and only assignment a let statement permits.
Unfortunately, it counts. This behavior is fixed into current style after this update:
Xcode Release Notes > Xcode 6.3 > Swift Language Changes
You may have found some workarounds, but I think this would be very near to what you want:
class MyView: UIView {
private(set) var myLayer: MyLayer!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.myLayer = MyLayer(owner: self)
self.layer.addSublayer(self.myLayer) }
}
You could use a lazy var for this. It’s basically a let, and only gets initialized once, at the first use site.
class MyView: UIView{
lazy var myLayer: MyLayer = {
return MyLayer(owner: self)
}()
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
self.layer.addSublayer(self.myLayer)
}
}

Swift View and Controller shared variable initialization troubles

I'm porting some code from Objc to Swift. And struggling with Swift's initialization life cycle. From a simplified point of view, there are 4 players I'm trying to bring into play here:
An object called a Program. This is my main top level model object at this point. The remaining 3 players all want access to an instance of him.
A ProgramEditController, painted in my main Main.storyboard. He's responsible for instantiating an initial Program, which cannot be done directly as a property initializer.
A top level custom UIView subclass, called ProgramTimelineView. Painted in the Main.storyboard, manages a variety of specialized subviews. Linked to a property of my ViewController. Has properties for the it's subviews as well. It wants access to the Program, so it can do layout and pass it on to subviews.
A particular subview of ProgramTimelineView called ProgramGridView. These are not painted in the XCode canvas tool, but directly instantiated by the containing ProgramTimelineView. It wants access to the Program. Uses it to do his custom drawRect.
Here's the relevant code for my Controller:
class ProgramEditController: UIViewController {
// MARK: - Variables
#IBOutlet var timelineView:ProgramTimelinesView!
var site = Site()
var program:Program!
// MARK: - Initialize
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
// set up the new program
self.program = self.site.newProgram()
// get it into our top view before it starts drawing
self.timelineView.program = self.program
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder) // Why does Swift make me have a redundant thing here?
}
}
And for ProgramTimelinesView:
class ProgramTimelinesView: UIView {
// MARK: - Variables
var gridView = ProgramGridView()
var program:Program! {
didSet {
self.gridView.program = self.program
}
}
// MARK: - Initialization
func addGridView() {
self.gridView.alpha = 0.0
self.gridView.opaque = false
self.addSubview(self.gridView)
}
func commonInit() {
self.addGridView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
}
And finally for the ProgramGridView:
class ProgramGridView: TimeAxisView {
// MARK: - Variables
var program:Program!
override func drawRect(rect: CGRect) {
// access self.program in here
}
}
What I thought would happen:
ProgramEditController.init(nibName...) would fire first.
The super call would cause my ProgramTimelineView.init(coder) to fire.
ProgramTimelineView instance would first call the gridView initializer setting it to a new ProgramGridView view
The remainder of ProgramTimelineView.init(coder) would run, which would add the gridView into the view tree.
Control would return to the ProgramEditController.init(nibName) initializers. The controller's program property would be populated.
The bound timelineView would have its program property set.
ProgramTimelineView would in turn set the program property of the gridView.
What seems to happen though, between steps 4 and 5, is that a drawRect() happens. That causes a seg fault, because the gridView's program has not been set yet! But why is it issuing drawRect()'s at this point? I thought that wouldn't happen before all of the initializers had fired. But clearly some side affect is occurring. What is the correct pattern/idiom to employ to avoid this? I really would rather not turn all of the program! into program? and then put let/guards every where.
There turned out to be a faulty assumption in my original premises (usually the case).
UIViewController.init(nib...) is NOT called when creating from interface builder assets. But local variables linked in interface builder (implicitly wrapped) are not set/realized at the point of init(coder) either. The correct approach required two adjustments:
Move the setup of the program var to the init(coder) initializers:
e.g.
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.program = self.site.newProgram()
}
Forward that to the view in a viewDidLoad() override.
e.g.
override func viewDidLoad() {
super.viewDidLoad()
self.timelineView.program = self.program
}