UIPageViewController : Scroll Delegate - ios

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.

Related

How to hide and show WKInterfaceGroup programmatically with animation?

The solution in this question* uses setHidden to hide and unhide a WKInterfaceGroup:
atypeofGroup.setHidden(true)
atypeofGroup.setHidden(false)
But the problem is, the group will appear and disappear abruptly, it doesn't look professional. Can someone guide me please? Not sure whether it is related to this:
atypeofGroup.animationDidStart(anim: CAAnimation!)
*hide and show WKInterfaceGroup programmatically
This is a great question, but it just isn't possible to animate a change between two groups with the current implementation of WatchKit. I definitely wish it was as well.
The only options you have are to switch interface controllers entirely through the reloadRootControllersWithNames:contexts: or to show/hide a couple of groups using the approach you listed first. Here's a small example of how you could switch from a SimpleInterfaceController to a FirstInterfaceController and SecondInterfaceController in a page set.
class SimpleInterfaceController : WKInterfaceController {
override func willActivate() {
super.willActivate()
let names = ["FirstInterfaceIdentifier", "SecondInterfaceIdentifier"]
WKInterfaceController.reloadRootControllersWithNames(names, contexts: nil)
}
}
I am not sure where you found the following code snippet, but it is certainly not part of the public APIs on WKInterfaceGroup.
atypeofGroup.animationDidStart(anim: CAAnimation!)
While I understand none of these answers are ideal, they are all we have access to at the moment. If you have the time, I'd suggest filing a feature request on Apple's bug reporting system.

Should UIAlertView be subclassed?

I am sure the answer to this is "no" as the documentation is very clear. But I am a little confused. A standard UIAlertView is pretty dull and I want to improve the look and it seems that other apps do it (see the example below).
Another possibility is that they are not subclassed UIAlertViews. In which case, how is this achieved?
The page UIAlertViews states
Appearance of Alert Views
You cannot customize the appearance of alert views.
So how do we get the something like the example shown here?
No, do not subclass it. From the docs:
Subclassing Notes
The UIAlertView class is intended to be used as-is
and does not support subclassing. The view hierarchy for this class is
private and must not be modified.
What you can do though is create a UIView and have it act similar to a UIAlertView. It's isn't very difficult and seems to be what they are doing in your op.
Apple's docs say that you should not subclass it. That means that there are probably internal reasons that would make it difficult to make it work right.
You might or might not be able to make a subclass of UIAlertView work, but you do so at your own risk, and future iOS releases might break you without warning. If you tried to complain Apple would laugh and tell you "I told you so".
Better to create a view that looks and acts like an alert but is your own custom view/view controller. Beware that even this is dangerous, because Apple has been making sweeping changes to the look and feel of it's UI elements recently. If you implement a view controller that looks and acts like a variant of the current alert view, Apple could change that look and/or behavior in the future and your UI app would end up looking odd and outdated. We've been bitten by this sort of thing before.
Rethink your strategy. Why do you need to use an Alert View? Besides having a modal view displayed top-most on your view stack, there's not much else that it does. Instead, subclass UIView or UIViewController to define your own interface, using images and ui elements to give it the style and input functionality as needed.
I usually subclass UIView, and attach it to the app's window's view so that I'm certain that it will be displayed on top of anything else. And you can use blocks to provide hooks into the various input elements of your new view (did user press OK, or did user enter text?)
For example:
// Instantiate your custom alert
UIView *myCustomAlert = [[UIMyCustomUIViewAlert alloc] initWithFrame:CGRectMake(...)];
// Suppose the new custom alert has a completion block for when user clicks on some button
// Or performs some action...
myCustomAlert.someEventHandler = ^{
// This block should be invoked internally by the custom alert view
// in response to some given user action.
};
// Display your custom alert view
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
[window addSubview: myCustomAlert];
// Make sure that your custom alert view is top-most
[window bringSubviewToFront: myCustomAlert];
Using this method, however, will not pause the thread's execution like UIAlertView does. Using this method, everything will continue running as usual. So if you need to pause execution while your custom alert is showing, then it gets much trickier.
But otherwise, creating your own custom alerts is quite straightforward, just as you would customize any other view. You could even use Interface Builder.
Hope this helps.
No. You absolutely should not subclass a UIAlertView for any reason. Apple explicitly states this in their documentation (see "Subclassing Notes"). They even tell you that it relies on private methods - and we all know that meddling in private methods in an AppStore app is immediate grounds for rejection.
HOWEVER, there isn't a need to subclass UIAlertView on iOS 7. Apple introduced a new Custom ViewController Transitions feature in iOS 7.0 that lets you present completely custom ViewControllers with completely custom transitions. In other words, you could very easily make your own UIAlertView or even something better. There's a nice tutorial on the new feature here:
In fact, there are lots of good tutorials on this - a quick Google search on the topic turns up a huge wealth of information.

UIRefreshControl Inside of UITableView Causing App to Freeze on Rotation - iOS 6+

Ok StackOverflow People...I've got a very interesting problem that I've been trying to solve for days and can't figure out so I need some major help. This will most likely be a very lengthy description but please bear with me and thank you deeply in advance for reading all of this because the more words I have, the clearer I can describe the full picture to you all. I will do my absolute best to be as terse and coherent as I can possibly be. Please let me know wherever I fall short.
Here's the context of my problem: I'm using Storyboards for my iOS app and for a particular nav tab in my app, I had to create two separate scenes for both the Portrait and Landscape orientations. The reason for doing this (instead of say, using Autolayout), is because within this said tab, there are visual elements (table views, web views, etc.) that are laid out differently depending on the orientation and it was a lot easier to create a separate orientation scene to handle this change in the UI instead of doing it programmatically -- (it's also just a lot easier to understand and cleaner code-wise). So the take away to keep in mind from all of this is that these two separate Portrait and Landscape scenes represent the SAME TAB in my app. (Side Note: these scenes were made in the IB of course)
Now the visual elements that I mentioned in the UI earlier -- going deeper, they are all containers for different UIViewControllers. I sandboxed everything in the app and pretty much have a 1-to-1 relationship for all things so these containers will map to my subclassed UIViewControllers that I've created for their specific purposes -- but it's here that the first caveat of my problem arises. Here's a practical example for a clearer picture, I have one UIViewController that contains a UITableView called MXSAnnouncementsViewController and this same view controller exists in both the Landscape and Portrait scenes. I did not create an explicit Portrait or Landscape VERSION of that view controller but instead, have the controller keep track of two IBOutlet properties (tableViewLandscape and tableViewPortrait) that point to the orientation-specific UITableViews -- and this approach works perfectly fine. Moreover in my MXSAnnouncementsViewController, I have a local property called tableView that abstracts the orientation-specific table views. It gets set within viewDidLoad which you can see below:
- (void)viewDidLoad
{
[super viewDidLoad];
if (self.tableViewPortrait) {
self.tableView = self.tableViewPortrait;
} else {
self.tableView = self.tableViewLandscape;
}
[self.tableView setDelegate:self];
[self.tableView setDataSource:self];
if (![MXSAnnouncementManager sharedAnnouncementManager].latestAnnouncements) {
[MXSAnnouncementManager loadModel:#"MXSAnnouncementGroupAllAnnouncements" withBlock:^(id model, NSError *error) {
if (!error) {
self.arrayLatestAnnouncements = [MXSAnnouncementManager sharedAnnouncementManager].latestAnnouncements;
[self.tableView reloadData];
} else {
// show some error msg
}
}];
} else {
self.arrayLatestAnnouncements = [MXSAnnouncementManager sharedAnnouncementManager].latestAnnouncements;
}
[self setupPullToRefresh];
}
Whenever I'm in the tab, one of the two orientation-specific IBOutlets is always active and has an address in memory while the other is nil. Whenever I rotate, the roles reverse -- whatever had an address in memory previously is now nil and the other has been initialized and allocated which is why I do what I did with the tableView property in the snippet above. Here is where caveat #2 comes into the picture and it's a doozy -- it has to do with the view lifecycle. Here's a practical example for clarity sake: Say I load the app up in Landscape orientation. When I do, my tableViewLandscape outlet has an address in memory and my tableViewPortrait outlet is nil. That's the expected and desired behavior. Now, when I rotate the app, the crazy stuff begins. Here's one place where I need clarity from all of you with regards to instances of UIViewControllers and what's normal vs. what's not so read the following VERY slowly and carefully.
Rotating the app immediately causes the opposite orientation scene (another INSTANCE of MXSAnnouncementsViewController???) to call its viewDidLoad method (in this example, we're in Landscape so the Portrait scene invokes that method). In that method, my local tableView property gets set to the currently active table view for that orientation (see snippet above). When that method finishes, the previous LANDSCAPE instance of MXSAnnouncementsViewController invokes its viewWillDisappear method which is then followed by the PORTRAIT instance's invocation of its viewWillAppear method which then lastly ends with the LANDSCAPE instance calling its willRotateToInterfaceOrientation callback -- that's the order of operation that I'm seeing from the breakpoints. I really do hope you got all of that because my mind just blew up from it all.
If you're still with me at this point, thank you because we're finally at the home stretch. As the title of this post suggests, the problem I'm trying to solve is my app freezing on rotation. If you haven't noticed on the viewDidLoad snippet, the last instruction to get executed is the setupPullToRefresh method which is the following:
- (void)setupPullToRefresh
{
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(refreshTableView:) forControlEvents:UIControlEventValueChanged];
[self.tableView addSubview:refreshControl];
}
Since I already explained the whole view lifecycle order of operations on rotation earlier, to make a very long story short, if I comment out that last setupPullToRefresh instruction at the end of viewDidLoad for MXSAnnouncementsViewController, my app works fine. If I include that instruction, my app becomes totally unresponsive on the first rotation and I cannot for the life of me figure out why. Not sure if I'm dealing with an edge case here or something. Any and all insights are welcome and THANK YOU SO MUCH for reading all of this!
Your best approach is probably to abandon your current design of having two separate controllers for portrait and landscape. On iOS, you should always relayout the views for the orientation you want to be in, not destroying and recreating everything. By trying to handle it by recreating everything, you're just going to get yourself in trouble I think.
You can use auto layout to do complex reorderings of views upon rotation if you know it well, but probably your best bet is to scrap your current code to do landscape, and write code to simply rearrange the views yourself upon rotating. You'll have far fewer issues down the road, and your code will be easier for others to understand and maintain as well.
When you remove that one bit of code, your app may appear to be working just fine, but there is probably something going on behind the scenes that isn't quite correct that could come back to bite you in the future. That's probably why adding the line of code breaks it.
Try to add it after rotation
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
[self setupPullToRefresh];
}
If that doesn't help, create UIRefreshControl only once and set it to the right table on rotation.
If that doesn't help too, follow the first given answer (#Gavin's answer) and create only 1 table on viewDidLoad and relayout things in -(void)viewWillLayoutSubviews

Clean way to force view to load subviews early

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

Override setContentOffset

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

Resources