is it allowed to override the setContentOffset method of an UIScrollView while subclassing?? Is this AppStore save?
sample:
-(void)setContentOffset:(CGPoint)contentOffset {
[super setContentOffset:contentOffset];
NSLog(#"co: %#",NSStringFromCGPoint(contentOffset));
if(_willScroll)
[_svDelegate setContentOffset:CGPointMake(contentOffset.x, contentOffset.y *2)];
}
thanks,
Omid
Yes. Lots of iPhone programmers subclass UIScrollView and then replace or extend functionality of public API's like setContentOffset.
The only correction I would make for you is to use the correct API. It's not:
setContentOffset:
but instead it's
setContentOffset: animated:
(i.e. with an animated parameter -- Apple's documentation is linked for you there).
In general this is no Problem to override some of these methods. You only add behaviour. Apple only refers to Interface Usability and so on.
So if your App feels still the "Apple-Way" it is all ok. https://developer.apple.com/appstore/resources/approval/guidelines.html
Related
I have a class that defines all styles on a UIVIew.
They are all predefined but I'm not sure when to fire this.
When I try to create an extension for this:
extension UIView
{
func willMoveToSuperview(newSuperview: UIView?)
{
self.stylize() // Another extension somewhere (not here my problem)
}
}
And I'm getting this error:
Method 'willMoveToSuperview' with Objective-C selector conflicts with
previews declaration with the same Objective-c selector
I have tried to override it, but didn't worked either.
Any ideas on how to be able to apply a same behaviour when all of my UIViews will become visible?
You can use Swizzling technic to customize UIView's function. Take a look at:
http://nshipster.com/method-swizzling/ (objective-c)
or
http://nshipster.com/swift-objc-runtime/ (swift)
Hope that helps.
Even though Swift's Extensions are similar to Categories from Objective-C, what you are trying to do is not allowed in Swift.
You cannot override existing functionality:
Extensions can add new functionality to a type, but they cannot override existing functionality.
Source: Swift Extensions - Apple Documentation
Depending on what it is that you are trying to style, you might want to take a look at UIAppearance, it will allow you to style default colors for the UINavigationBar, amongst other things. NSHipster has a good post about it: NSHipster - UIAppearance
You can create a subclass of UIView with the method .stylize().
Then each view you create, you inherit of you UIView subclass.
You'll be able to cal .stylize() on each UIViewSubclass. Simply write the style code inside the subclass and inherite.
Or
Use a category to add the method to the existing UIView class.
See : https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html
Outside of swizzling (not generally recommended), or subclassing as noted by David in his answer, there isn't really a way to override existing methods on a class and its subclasses.
One thing you might try is creating a base class for your view controller instead of all your views. In your view controller base class, you could override viewWillLayoutSubviews to recurse through the view hierarchy and call stylize on each view. This means you would be using the subclass approach in fewer places (just view controllers as opposed to all views).
Another thing you might consider if taking the subclassing approach with UIView is that if you are subclassing anyway, you can take advantage of things like #IBDesignable and #IBInspectable to better integrate those UIView subclasses with storyboards and live preview.
I wrote a Swift library which does exactly this, and it works well for the type of styling it seems you want to do: https://github.com/daniel-hall/Stylish
I'm in the situation where one of the viewControllers of my app is getting pretty big, specially since I've added a "Tutorial state" which adds a different implementation for many methods of this class that I control by checking
_tutorialEnabled?
So, my question is if this is a good use case for method swizzling, I could have this different implementations of these methods in a separate category and swizzle them when required, it might help me reduce the amount of code of the default implementation. Any comments or suggestions of other techniques are appreciated.
No, this is not what method swizzling was designed for.
Personally I would create a subclass of the view controller that manages tutorial related stuff. Then, depending on whether or not the tutorial is enabled, you instantiate either the tutorial controller or its superclass. This is what polymorphism was designed for: to avoid endless if/else/switches.
Why don't you subclass? Create a tutorial subclass of your view controller with all the needed logic. Present the tutorial in the real view controller's -viewDidAppear: using a full screen modal without animation. When the tutorial is over dismiss the model without animation.
if _tutorialEnabled != nil && _tutorialEnabled {
tutorialViewController = …
tutorialViewController.modalPresentationStyle = .FullScreen
presentViewController(tutorialViewController, animated: NO) {}
}
No, I wouldn't use method swizzling for this. It's a bit like using a sledgehammer to knock in a thumbtack.
Unlike others I also would not subclass a view controller, maintaining understandable flow around view lifecycle events is really important when you want to add other functionality later.
Instead I would use the strategy pattern for this. In your init you could do something like this:
if (tutorialEnabled) {
self.behaviour = [TutorialBehaviour new];
} else {
self.behaviour = [NormalBehaviour new];
}
Then when you need to do something that changes you just call a method on your behaviour eg.
- (void)viewDidLoad
{
...
[self.behaviour load]
...
}
In my iOS app, I would like to set the delegate of my PageViewController's main UIScrollView.
There is tons of answer that say : "Ok man, juste iterate over subview and you will find the scrollview".
Ok, it works, but does it pass the Apple Validation Step ? Is it not a call to private API ? Does someone successfully published an app with this trick ?
Thanks everyone,
In short, no, this is not going to trip Apple's validation step. See this answer for a list of ways Apple detects private API usage. They're mostly looking for if you reference a private class or selector. For instance UIKeyboardImpl or _setViewDelegate:.
I personally have something very similar in the app store. I needed to get the UITextField in a UISearchBar so I have the following code:
UIView<UITextInput> *UISearchBarTextInput(UISearchBar *searchBar) {
for (id view in searchBar.subviews) {
// Could be in the top level. (iOS6)
if ([view conformsToProtocol:#protocol(UITextInput)]) {
return view;
}
// Or the next level. (iOS7)
for (id subview in [view subviews]) {
if ([subview conformsToProtocol:#protocol(UITextInput)]) {
return subview;
}
}
}
return nil;
}
The big problem you are likely to run into is that you are dealing with a private view hierarchy and Apple makes no guarantees that this will stay constant between iOS releases. In the code above I have to support two different iOS versions. If they decided to make a change in iOS9 I would have to make modify my code. It's a good practice in this case to assume that your lookup may fail (you won't be able to find the scroll view or a new scroll view may be added) and make your code resilient to that case.
Also, you have to consider that a human will use your app as part of the app store review. If you change things too much from the expected behavior it can be rejected on that alone.
I've tried for some days understand Xcode Subclasses and Categories - and after all I found one event that are fired.
- (void)setContentOffset:(CGPoint)contentOffset {
NSLog(#"foo");
}
And for more confusion, after read Apple iOS Documentation I get this stuff:
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated {
NSLog(#"bar");
}
First event are fired, but from Apple documentation are not. Why?!
But in the first case, although he was fired the UIScrollView loses their scroll/drag'n' bounce behavior. I think it's because after overrride setContentOffset I would need to call the parent method to keep the default behavior of the UIScrollView. But I'm already exhausted from test obsolete Xcode approaches.
Than why second code are not fired and how call parent overridden method?
Thanks in advance.
To call the super (:parent) here
- (void)setContentOffset:(CGPoint)contentOffset {
NSLog(#"foo New Offset x: %.0f y: %.0f", contentOffset.x, contentOffset.y);
[super setContentOffset:contentOffset];
}
And, for the second one; That is not a delegate method (:event), this is a method provided to developer actually, to initiate scrolling to a specific offset with/without animation. You probably do not need to override this.
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;
Even more; even the first one is not an event, that's a message sent to scrollview to change the offset, but you can get in between and do your thing using that as an event trigger, and call super again to let it do it's work.
If you want to get real events on scrollView, you need to set up a delegate as documented here;
https://developer.apple.com/library/ios/documentation/uikit/reference/UIScrollViewDelegate_Protocol/Reference/UIScrollViewDelegate.html#//apple_ref/occ/intf/UIScrollViewDelegate
And I also agree with Wain on sharing this link,
https://developer.apple.com/library/ios/documentation/general/conceptual/DevPedia-CocoaCore/Delegation.html
Recently I wrote some code where I tried to refer to an outlet on a UIViewController I'd just instantiated with [storyboard instantiateViewControllerWithIdentifier] and modify the subview that the outlet pointed to before presenting the ViewController. It didn't work because the ViewController's view hadn't loaded its subviews yet, including the one that my outlet referred to, so the property just gave me a null pointer.
After (with some struggle) tracking down the cause of my issue in the debugger, I Googled around and learned, through answers like this one, that I can cause the view to load its subviews without being displayed by calling the myViewController.view getter. After that, I can access my outlet without any problems.
It's a clear hack, though, and Xcode - quite rightly - doesn't like it, and angrily protests with this warning:
Property access result unused - getters should not be used for side effects
Is there a non-hacky alternative way to do this that doesn't involved abusing the .view getter? Alternatively, are there canonical/idiomatic patterns for this scenario involving something like dynamically adding a handler to be called as soon as the subviews are loaded?
Or is the standard solution just to replace myViewController.view with [myViewController view] to shut up Xcode's warning, and then live with the hack?
On iOS 9 or newer, one can use:
viewController.loadViewIfNeeded()
Docs: https://developer.apple.com/reference/uikit/uiviewcontroller/1621446-loadviewifneeded
I agree that forcing a view to load should be avoided but I ran into a case where it seemed the only reasonable solution to a problem (popping a UINavigationController containing a UISearchController that had yet to be invoked causes a nasty console says warning).
What I did was use new iOS9 API loadViewIfNeeded and for pre-iOS9 used viewController.view.alpha = 1.0. Of course a good comment above this code will prevent you (or someone else) removing this code later thinking it is unneeded.
The fact that Apple is now providing this API signals it can be needed from time to time.
Not sure how much cleaner this way, but it still works fine:
_ = vc.view
UPD: for your convenience, you can declare extension like below:
extension UIViewController {
func preloadView() {
let _ = view
}
}
You can read explaination by following URL: https://www.natashatherobot.com/ios-testing-view-controllers-swift/
merged Rudolph/Swany answers for pre ios9 deployment targets
if #available(iOS 9.0, *) {
loadViewIfNeeded()
}
else {
// _ = self.view works but some Swift compiler genius could optimize what seems like a noop out
// hence this perversion from this recipe http://stackoverflow.com/questions/17279604/clean-way-to-force-view-to-load-subviews-early
view.alpha = 1
}
If I understand you correctly, I think there's another fairly standard solution: move the outlet modification/configuration code into a viewDidLoad method (of the recently instantiated VC).
The topic is also discussed in this question.
It would require some restructuring, but it might give you a "cleaner" design in terms of MVC if your incoming VC handled its own configuration, and it would avoid the "You should never call this method directly" stricture on loadView.
You can call [myViewController loadView] to explicitly load the view, instead of abusing the .view getter. The .view getter actually calls loadView if necessary when called.
It's still not a very nice solution, since the UIView Documentation's section on loadView explicitly instructs that
You should never call this method directly