I believe everyone is familiar with
open func didAddSubview(_ subview: UIView)
This function is called whenever a new subView is added to the UIView.
I am wondering on how to achieve? Like, what is the underlying codes to achieve this?
Thanks
Edit:
what i mean in the question is the codes to achieve a non-objective-c observing function. For example, we can build a notification, so that whenever addSubView is called, the didAddSuView is called correspondently, but apparently, in this case. It is not an object-c selector which we see in normal implementation. So i am asking how do we achieve something like this, which means, how do i trigger a function when another function is called without it being an objc function.
Btw, just to clarify further. The function is implemented as function of UIView (Extension UIView), so it is not a protocol, which means it wasn't elegantly achieved as delegate.
So again, i am asking.. How can i replicate something like this?
“The underlying codes” is that you just call the “observing function” when it needs to be called. There's no magic in how UIKit calls didAddSubview. It doesn't use any features specific to Objective-C.
The iOS SDK has four methods for adding a subview to a view:
addSubview:
insertSubview:atIndex:
insertSubview:aboveSubview:
insertSubview:belowSubview:
All of these methods are wrappers for a private method, _addSubview:positioned:relativeTo:. You can check this using a disassembler, or by putting a breakpoint in didAddSubview: and looking at the stack trace to see who calls it.
The private method calls [self didAddSubview:subview] using a normal Objective-C message. It does not “build a notification”. The source code probably looks something like this:
- (void)_addSubview:(UIView *)newSubview position:(UIViewSubviewPosition)position relativeTo:(UIView *)sibling {
// Lots of bookkeeping related to first responder status,
// gesture recognizers, auto layout, visual effects,
// and private implementation details…
[newSubview removeFromSuperview];
switch (position) {
case UIViewSubviewPositionAtEnd:
[self.layer addSublayer:newSubview.layer];
break
case UIViewSubviewPositionBelowSibling:
[self.layer insertSublayer:newSubview.layer below:sibling.layer];
break;
case UIViewSubviewPositionAboveSibling:
[self.layer insertSublayer:newSubview.layer above:sibling.layer];
break;
default:
[self.layer insertSublayer:newSubview.layer atIndex:(unsigned int)position];
break;
}
[newSubview didMoveToSuperview];
[newSubview didMoveToWindow];
[self didAddSubview:newSubview];
// Lots more bookkeeping related to first responder status,
// gesture recognizers, auto layout, visual effects,
// and private implementation details…
}
In Swift, it could look like this:
enum SubviewPosition {
case atEnd
case below(UIView)
case above(UIView)
case atIndex(UInt32)
}
func _addSubview(_ newSubview: UIView, position: SubviewPosition) {
// Lots of bookkeeping related to first responder status,
// gesture recognizers, auto layout, visual effects,
// and private implementation details…
newSubview.removeFromSuperview()
switch position {
case .atEnd: layer.addSublayer(newSubview.layer)
case .below(let sibling):
layer.insertSublayer(newSubview.layer, below:sibling.layer)
case .above(let sibling)):
layer.insertSublayer(newSubview.layer, above:sibling.layer)
case .atIndex(let index):
layer.insertSublayer(newSubview.layer at:index)
}
newSubview.didMoveToSuperview()
newSubview.didMoveToWindow()
didAddSubview(newSubview)
// Lots more bookkeeping related to first responder status,
// gesture recognizers, auto layout, visual effects,
// and private implementation details…
}
Related
I have a UIView with two buttons on it. In the MyView class I have this code:
-(BOOL) canBecomeFocused {
return YES;
}
-(NSArray<id<UIFocusEnvironment>> *)preferredFocusEnvironments {
return #[_editButton, _addButton];
}
-(IBAction) editTapped:(id) sender {
BOOL editing = !tableViewController.editing;
[_editButton setTitle:editing ? #"Done" : #"Edit" forState:UIControlStateNormal];
_addButton.hidden = !editing;
[tableViewController setEditing:editing animated:YES];
}
The basic idea is that the user can move the focus to the edit button, which can then make the Add button appear.
The problem started because every time I tapped the edit button, focus would shift to the table view. I would actually like it to move to the Add button. I also want it so that when editing it deactivated, the edit button keeps the focus. but again it's shifting down to the table view.
So I tried the above code. This works in that focus can move to the view and on to the button. But once it's there, I cannot get it to move anywhere else.
Everything I've read says just override preferredFocusEnvironments but so far I've not been able to get this to work. Focus keeps going to a button then refusing to move anywhere else.
Any ideas?
If anybody is facing this issue, Just check if you are getting the following debug message printed in the console.
WARNING: Calling updateFocusIfNeeded while a focus update is in progress. This call will be ignored.
I had the following code :
// MARK: - Focus Environment
var viewToBeFocused: UIView?
func updateFocus() {
setNeedsFocusUpdate()
updateFocusIfNeeded()
}
override var preferredFocusEnvironments: [UIFocusEnvironment] {
if let viewToBeFocused = self.viewToBeFocused {
self.viewToBeFocused = nil
return [viewToBeFocused]
}
return super.preferredFocusEnvironments
}
I was calling the updateFocus() method multiple times while viewToBeFocused was either nil or some other view. Debugging the focus issues mainly between transition is really difficult. You should have patience.
Important to note: This depends on your use case, but if you want to
update the focus right after a viewcontroller transition (backward
navigation), You might have to set the following in viewDidLoad:
restoresFocusAfterTransition = false // default is true
If this is true, the view controller will have the tendancy to focus the last focused view even if we force the focus update by calling updateFocusIfNeeded(). In this case , since a focus update is already in process, you will get the warning as mentioned before at the top of this answer.
Debug focus issue
Use the following link to debug the focus issues: https://developer.apple.com/documentation/uikit/focus_interactions/debugging_focus_issues_in_your_app
Enable the focus debugger first under Edit scheme > Arguments passed on launch:
-UIFocusLoggingEnabled YES
This will log all the attempts made by the focus engine to update the focus. This is really helpful.
You can override the preferredFocusEnviromnets with the following logic:
-(NSArray<id<UIFocusEnvironment>> *)preferredFocusEnvironments {
if (condition) {
return #[_editButton];
}
else {
return #[_addButton];
}
}
After setting it, you can call
[_myView setNeedsFocusUpdate];
[_myView updateFocusIfNeeded];
The condition could be BOOL condition = tableViewController.editing; or sg like that.
If that now works, you can call it with a delay (0.1 sec or so).
I'm very new to Swift, and trying to create an app where Swiping between pages works normally but also involves a change in background color based on swipe distance.
Consequently, I want to "hijack" the swipe gesture, so I can add some behavior to it. The best I can find for how to do that is this question/answer.
Translating the code from the chosen answer into Swift 3, I added the following code to my RootViewController's viewDidLoad function:
for subview in (pageViewController?.view.subviews)!{
if let coercedView = subview as? UIScrollView {
coercedView.delegate = (self as! UIScrollViewDelegate)
}
}
My impression was that the above code would let me delegate functions (such as scrollViewDidScroll to the class in which I'm writing the above code, such that I can define that function, call super.scrollViewDidScroll, and then add any other functionality I want.
Unfortunately, the above code, which doesn't throw any compilation errors, does throw an error when I try to build the app:
Could not cast value of type 'My_App.RootViewController' (0x102cbf740) to 'UIScrollViewDelegate' (0x1052b2b00).
Moreover, when I try to write override func scrollViewDidScroll in my class, I get a compilation error telling me the function doesn't exist to override, which makes me think even if I got past the error, it wouldn't get called, and this isn't the right way to handle this issue.
I'm sorry this is such a noobish question, but I'm really quite confused about the basic architecture of how to solve this, and whether I understand the given answer correctly, and what's going wrong.
Am I interpreting delegate and that answer correctly?
Am I delegating to the correct object? (Is that the right terminology here?)
Is there a better way to handle this?
Am I coercing/casting improperly? Should I instead do:
view.delegate = (SomeHandMadeViewDelegateWhichDefinesScrollViewDidScroll as! UIScrollViewDelegate)
or something similar/different (another nested casting with let coercedSelf = self as? UIScrollViewDelegate or something?
Thanks!
If I understand your question correctly, you want to catch some scroll position and stuff right ? Then do
class RootViewController {
// Your stuff
for subview in pageViewController!.view.subviews {
if let coercedView = subview as? UIScrollView {
coercedView.delegate = self
}
}
extension RootViewController : UIScrollViewDelegate {
// Your scrollView stuff there
}
This seems like it should have a simple answer, and probably does, but it's proving harder to find than I expected. As a specific example, let's say that I'm programming a chess game.
It seems like this is something I should be able to do just using CoreGraphics. It seems like using OpenGL or SpriteKit shouldn't be necessary.
I want to have a Board class that models the state of the board. Where should I declare my Board object? My impression is that it should be in the ViewController.
I want to have a view (actually a subview) that displays the current state of the board (by overloading drawRect). It should do this at the beginning, and should be updated when players make moves. How does the view access the data model to display the board state? Does giving the view a reference to the data violate MVC? If not, how would the reference be passed to the view? (I seem to just find lots of links about passing data between two ViewControllers.)
Should it instead be the ViewController "pushing" the data to the view whenever it needs to be drawn? My understanding, though, is that drawRect should not be called directly, and that instead setNeedsDisplay should be called, which will indirectly result in drawRect being called. This being the case, it's hard to see how the data would be passed.
Your code; your design decision. Nothing to comment on here.
You should have your model declaration in ViewController. True. That is how MVC works.
Having a reference of the data in a UIView DOES break MVC. Your view instance will not be independent anymore. Decoupling view and model is one of the main points of MVC and you are probably breaking it with this design.
What can you do about it?
Extending #Paulw11's comment, in your view controller you can declare a method that looks something like this :
func movePiece(somePiece : Piece, toSquare : Square) {
let pieceID = somePiece.id //I am just assuming the models structures
let pieceImageView = self.pieceImageViewFromID(id) //Assume that this method returns the reference of the image view. Assume that I am just working UIKit here.
let fromPoint : CGPoint = somePiece.origin
let toPoint : CGPoint = toSquare.coordinates
self.animateView(pieceImageView, fromPoint:fromPoint, toPoint:toPoint)
}
Note that in this design, the view is not holding any model references; the view controller will take care of setting its state and bring upon relevant animations.
If you are overriding drawRect:, then yes, for it be called, you should call setNeedsDisplay to update the changes. The view controller might call or you can add property observers to redraw itself based on a property change. One example for this could be:
class SillyView : UIView {
var drawPonies : Bool = false {
didSet {
if oldValue != drawPonies {
self.setNeedsDisplay()
}
}
}
override func drawRect(rect: CGRect) {
if drawPonies {
self.drawGoodLookingPony()
} else {
self.drawSomeOtherAnimal()
}
}
func drawGoodLookingPony() {
//Draw a good looking pony here
}
func drawSomeOtherAnimal() {
//Draw Something else
}
}
If your view controller decides to draw ponies all you have to do is, get the reference of the SillyView and set drawPonies to true.
self.sillyView.drawPonies = true
You are not passing your data model here, but important pieces of configuration information that will help the view redraw itself.
I use the next code to setup UIPanGestureRecognizer in my component:
MyComponent *c =[super newWithView:{
[UIView class],
{
{
CKComponentGestureAttribute(
[UIPanGestureRecognizer class],
&setupPanRecognizer,
#selector(panGesture:context:),
{}
)
},
{
#selector(setUserInteractionEnabled:), #YES
}
}
}
component: [MyOtherComponent newOtherComponentWithMode:model context:context]];
I process panGesture:context in MyComponentController object.
My problem is that UIPanGestureRecognizer blocks feed view from scrolling. In order to fix this I want to use UIGestureRecognizerDelegate protocol and allow both (scroll view and my pan) recognizers to work simultaneously.
My question is how can I assign my component controller as a delegate for UIPanGestureRecognizer? setupPanRecognizer is just a C function and it does not have reference to MyComponentController object or even the component itself.
The only way I see now is to get list of gesture recognisers somewhere in didUpdateComponent method in my controller, find the right one and assign delegate there.
Does ComponentKit provide any solution to this?
CKComponentViewAttributeValue CKComponentGestureAttribute(Class gestureRecognizerClass,
CKComponentGestureRecognizerSetupFunction setupFunction,
CKComponentAction action,
CKComponentForwardedSelectors delegateSelectors)
You can find the "delegateSelectors" argument at last, so you could try to pass in a vector of selectors which you would like to response in the controller.
I've been wondering about extending UIView with a sort of setNeedsRefresh method of my own - a method that behaves like setNeedsLayout and setNeedsDisplay but with the purpose of refreshing/reloading visible data, hiding views, etc. My goals would be to:
Be able to detach methods (particularly setters) from having to instantly refresh views.
Be able to flag a view for refresh rather than refreshing it manually (in order to optimize code execution and have a cleaner code architecture).
I was hoping to be able to extend UIView in such a way that you can call setNeedsRefresh on any view, and have it execute refreshView or some similar method before redrawing happens.
PS: is the best way to handle this through setNeedsLayout + a refresh flag?
Update #1: The following is an example of what I want to be able to do:
-(void)setAvailableForPurchase:(BOOL)availableForPurchase
{
if (_availableForPurchase != availableForPurchase)
{
_availableForPurchase = availableForPurchase;
[self setNeedsRefresh];
}
}
And then:
-(void)refreshView
{
if (self.isAvailableForPurchase)
{
self.someView.image = someImage;
self.someButton.enabled = yes;
// more code, changes several objects
}
else
{
// some other code...
}
}
This example is a very simple one on purpose to better express my requirement - the reasons why I need/want to do this are listed in my initial question.