UIWebView's _UIWebViewScrollView - ios

I created an instance of UIWebView called myWebView. I populated myWebView with the content from loadHTMLString, added several UIImageViews and UIViews. It worked as expected: UIImageViews and UIViews are all on top of the webview's content from html string.
I then archived and un-archived myWebView using NSKeyedArchiver and NSKeyedUnarchiver. After un-archived, only the html string content is visible. All other UIViews and UIImageViews were moved to back. So they are not visible. An NSLog of myWebView.subviews before archiving and after un-archiving showed that there a _UIWebViewScrollView was moved from the first object in the subviews to the last object. Note that only the _UIWebViewScrollView order was changed.
Not sure if it was by design by the SDK but to restore the views order and make them visible as normal, I did this:
[myWebView sendSubviewToBack:[myWebView.subviews lastObject]];
Question: Will doing this violate any of Apple's rule as I am not sure what if the _UIWebViewScrollView is considered as Apple's private thing. But since I did not touch that _UIWebViewScrollView.

Well, as far as rules go, you're fine.
HOWEVER: Since the UIWebViewScrollView is an internal member of the UIWebView, if they change the workings of UIWebView in a different iOS version, your code might get screwed up.
To play it a little on the safer side, you should try to set tags on each of the subviews you add, and then do:
[myWebView bringSubviewToFront:[myWebView viewWithTag:100]];
[myWebView bringSubviewToFront:[myWebView viewWithTag:101]];
[myWebView bringSubviewToFront:[myWebView viewWithTag:102]];
... etc (remember to use the tags that you used for your subviews)
On another note, messing with the UIWebView might result in troubles later on, there might be other ways to accomplish what you're trying to do, ways that are less risky

Related

View Controller not Displaying Correctly

I must be missing something really basic here, but I don't even know how to simply describe the problem. I designed my view controller layout in my storyboard, just a bunch of image views, labels, constraints, etc. All static - the only change the code makes is to add round edges to the buttons (UIViews), but I've also tried without that code with no difference made.
Once my segue is followed to the second view controller, it displays like this on the iPhone:
I've gotten it to display properly in 2 cases (most of the time):
Waiting. Anywhere between 30sec to 10min. Very unpredictable
Pressing the home button then resuming the app. Sometimes the text still won't show up when I do this.
Either way, this is what it's supposed to look like:
It doesn't lag or anything as the UI is completely usable even when it's not displaying properly. The images are not very large (biggest is around 300x600) so I doubt that is an issue. Image size also wouldn't account for why the text isn't displaying either. Do UIImageViews load their images asynchrounously?
I thought it might have been that the views needed refreshing because of the exit/resume behaviour. I tried [eachView setNeedsDisplay] with no success.
Any ideas?
-- UPDATE 1 --
Here is an image of my storyboard. As I said, everything is highly static. I have no idea why it's not displaying.
-- Update 2 --
I tried adding my image files to a bundle as suggested:
Then tried to reference them pragrammatically in the viewDidLoad method:
// Interface
#property (weak, nonatomic) IBOutlet UIImageView *backgroundImage;
// viewDidLoad
self.backgroundImage.image = [UIImage imageNamed:#"background.png"];
No difference unfortunately.
I have had this issue using an xcassets file. It tends to work just fine if I just add them to the bundle directly in a group. Check out this thread for some more details.
Impossible to load an image in xcassets on bundle
Here's what the problem was for those having the same issue. The previous view controller to the problem screen was a login screen. After receiving credentials, the controller would asynchronously hit the server to validate them. The response from the server was then handled in a callback function called by the networking thread. This meant that the segue was being invoked off of the main thread. Oddly enough, the segue still performed fine I guess it just took a while for the main thread to realize what was happening.
TLDR: Make sure to performSegue on the main thread.

how to detect interface changes on iOS application

Is there any possible way to detect every change on User Interface during runtime??
I'm trying to find all objects in the current app interface.
I'm trying to to get all nodes inspecting recursively the main Window, but, for example, how to know if the top viewcontroller changes or if it's added a uiview dynamically, or is presented a modalview??
The main objective is to have a library to do this..
Any idea, help?
Thanks!
You could write your own library based on this, using advanced Objective-C techniques. I do not recommend you to do this, since it mostly breaks MVC patterns on iOS. Depends on what do you want to use it for, maybe analytics?
So these are the options I believe, if you want to actively inspect UIView hierarchy. All options are pretty complicated though.
Swizzle methods such as addSubview and removeFromSuperview of UIView, so you could know when changes like that happens. Including the getters of frame and bounds, if you wish to know the position.
You could use KVO to watch properties such as: subviews, frame, bounds, superview to notice any changes. But at one point you would have to add the same object as the observer (could be singleton).
Decide for an interval that is fired by a NSTimer and go through the hierarchy recursively beginning at keyWindow on UIApplication. This would have a big performance impact though.
There may be other options, but these are the ones I believe to be the best choices.

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

iOS 6 UIScrollview: Help My Taco App Keeps Crashing

I have a UIScrollView full of tacos.
I attached a pull-to-refresh handler to it via: https://github.com/samvermette/SVPullToRefresh
It extends uiscrollview, and exposes this method:
[scrollview addInfiniteScrollingWithActionHandler:^{
// Get me more tacos
}];
When InfiniteScrolling is triggered, I clear the scrollview's subviews and data array(intentionally) and replace it with a new set.
It works great the first time. However, when I want to load more tacos it crashes.
I get:
-[SVInfiniteScrollingView retain]: message sent to deallocated instance 0x1e5db5d0
Not surprisingly, if I leave 1 subview left in my UIScrollview, everything works fine.
Question: How can I fix this?
I thought about declaring my properties with a strong pointer, like:
#property (strong, nonatomic) IBOutlet tacoScroller *tacoScroller;
But, I worry about a retain cycle & it also doesn't work.
Any help would be appreciated, perhaps I'm missing something fundamental.
Edit:
I'm using ARC
Use an UITableView to show your tacos, this way you will reuse views and avoid wasting memory. Also it is the easiest and most convenient way to show a list of things.
One simplest solution according to your description is simply add an empty & hidden subview inside the scrollview, i am sure it wont occupy much amount of memory.
I think you are invoking the wrong method. Infinite scrolling is for other purposes.
You probably want to use
[scrollView addPullToRefreshWithActionHandler:^{ ... }];
Also, as others pointed out, you definitely should consider to use a UITableView to present your data, which seems to be very suitable for the task.

Where to place the code to change the properties of a UIView

If I'm creating a UIView programmatically and I wish to change the UIView properties (background, for example, or actually, messing with CALayers), must I place the code outside of UIView such as in the View controller? Can I put the code somewhere inside UIView?
I was checking out the CoreAnimationKioskStyleMenu example, its code is inside UIView but it's loaded from Nib and can be placed at awakeFromNib, so it doesn't seem to apply to my case.
That depends. Obviously, a good way to handle this is to use a xib file, as it is designed to hold data like this, but that isn't always the best answer for every situation.
If the view is meant to be reused frequently (like a button, or some widget) throughout the application, its best to store all that customization in a subclass of the UIView.
If its a single larger view that will always be managed by a UIViewController, you can keep some of the information in the UIViewController. However, if you end up subclassing a UIView anyway it's probably best practice to keep the data in the UIView.
As a general note, I believe its worth your time to push as much of this data into a xib using interface builder. Magic values (like colors or sizes) peppered through your code will always be a problem if you want to modify it. I have found modifying a xib to be much easier.
Actually there are some methods where you could place initialization/ customization code.
(void)willMoveToSuperview:(UIView *)newSuperview;
(void)didMoveToSuperview;
will get called as soon as u add the view as a subview to another view, at which point you already have the frame and all the properties, and you can do further customizing as you wish.
(void)layoutSubviews -- generally used for changing subviews' frames and layout organization.
Will get called each time the view needs to be redrawn by the system, or when you specifically call [self setNeedsLayout] on your UIView.
Hope this helps.

Resources