Lazy rendering print pages on iOS - ios

I need to print custom UI in a cross-platform application. On Windows, the work flow is like:
Choose what to print
Open the Windows Print dialog/print preview. This only queries for print pages as they are needed.
Begin printing and report progress as each page prints.
I am trying to replicate this workflow on iOS, but it seems that all the print pages need to be print-ready before opening the iOS print dialog (UIPrintInteractionController). This is a big problem because the user may be printing a lot of pages, and each page can take awhile to prepare for printing. Therefore it takes too long to prepare and open the UIPrintInteractionController.
Is there any way on iOS to have it lazily query for the print pages one (or even a few) at a time as they are being previewed or printed? Or do they all have to be prepared ahead of presenting the UIPrintInteractionController?
EDIT: It's a major problem when printing many pages because it can easily run out of memory holding that many UIImages in memory at the same time.
I tried using the PrintItems property with UIImages, as well as a UIPrintPageRenderer. I am using Xamarin.iOS so pardon the C# syntax, but you get the idea. An answer in Swift or Objective C would be fine. Here is my sample pseudo-code:
//Version 1
public void ShowPrintUI_UsingPrintingItems()
{
UIPrintInfo printOptions = UIPrintInfo.PrintInfo;
InitializePrintOptions(printOptions);
_printer.PrintInfo = printOptions;
var printRect = new CGRect(new CGPoint(), _printer.PrintPaper.PaperSize);
//Each of these is horribly slow.
//Can I have it render only when the page is actually needed (being previewed or printed) ?
for (uint i = 0; i < numPrintPages; i++)
{
_printPages[i] = RenderPrintPage(i, printRect);
}
_printer.PrintingItems = _printPages;
_printer.Present(true, (handler, completed, error) =>
{
//Clean up, etc.
});
}
//Version 2
public void ShowPrintUI_UsingPageRenderer()
{
UIPrintInfo printOptions = UIPrintInfo.PrintInfo;
InitializePrintOptions(printOptions);
_printer.PrintInfo = printOptions;
//This still draws every page in the range and is just as slow
_printer.PrintPageRenderer = new MyRenderer();
_printer.Present(true, (handler, completed, error) =>
{
//Clean up, etc.
});
}
private class MyRenderer : UIPrintPageRenderer
{
public MyRenderer(IIosPrintCallback callback)
{
_callback = callback;
}
public override void DrawPage(nint index, CGRect pageRect)
{
DrawPrintPage(i, printRect);
}
public override nint NumberOfPages => _numPrintPages;
}

Related

Xamarin forms geolocator plugin working intermittently

We are trying to use the plugin "Xam.Plugin.Geolocator" in our Xamarin Forms project. The project is currently IOS only.
Our app returns a list of business based on the device users current location. We hit an API to return our JSON formatted list data and the API is functioning correctly.
We would like the list to update whenever the user pulls down, changes tab and when the page initially loads but currently this is only working once or twice in around 100 attempts. I've not found a pattern yet to why it's failing, or indeed when it works.
We set App Properties when the page loads, the tab is selected and the user refreshes like this -
public async void GetLocation()
{
try
{
locator = CrossGeolocator.Current;
if (locator.IsGeolocationAvailable && locator.IsGeolocationEnabled)
{
var position = await locator.GetPositionAsync();
App.Current.Properties["Longitude"] = position.Longitude.ToString();
App.Current.Properties["Latitude"] = position.Latitude.ToString();
}
else
{
await DisplayAlert("Location Error", "Unable to retrieve location at this time", "Cancel");
}
}catch(Exception e)
{
await DisplayAlert("Location Error", "Unable to retrieve location at this time","Cancel");
}
}
We call the above method in the three areas
1) when the page is loaded
public NearbyPage()
{
InitializeComponent();
GetLocation();
SetNearbyBusinesses();
NearbyBusinesses = new List<NearbyBusiness>();
SetViewData();
SetViewVisibility();
}
2) when the tab is clicked
protected override void OnAppearing()
{
base.OnAppearing();
GetLocation();
SetNearbyBusinesses();
NearbyLocationsView.ItemsSource = NearbyBusinesses;
NoLocationsView.ItemsSource = UserMessages;
SetViewVisibility();
}
3) when the user pulls down to refresh
public void RefreshData()
{
if (!CrossConnectivity.Current.IsConnected)
{
NoInternetMessage.IsVisible = true;
return;
}
GetLocation();
NoInternetMessage.IsVisible = false;
SetNearbyBusinesses();
NearbyLocationsView.ItemsSource = NearbyBusinesses;
NoLocationsView.ItemsSource = UserMessages;
SetViewVisibility();
_analyticsService.RecordEvent("Refresh Event: Refresh nearby businesses", AnalyticsEventCategory.UserAction);
}
Can anyone shed some light on what we're doing wrong or have experience with this plugin that can help us resolve this issue?
Thank you
EDIT
By "work", i mean that we'd like it to hit our API with the users current location data and return new results from the API every time the user pulls down to refresh, the page is loaded initially or when they press on a specific tab. Currently it works occasionally, very occasionally.
We can't debug with a phone connected to a macbook, as since we installed the geolocator plugin the app always crashes when connected. The app seems to work ok when deployed to a device, apart from the location stuff. We're currently deploying to test devices via Microsofts Mobile Centre.
Ok, so with the debugger always crashing and being unable to see any stack trace etc we took a few shots in the dark.
We've managed to get this working by adding async to our method signatures down through our code stack. This has resolved the issue and the geo location and refresh is working perfectly.
For example when we changed the above method 3. to refresh the data, it worked perfectly.
public async Task RefreshData()
{
if (!CrossConnectivity.Current.IsConnected)
{
NoInternetMessage.IsVisible = true;
return;
}
GetLocation();
NoInternetMessage.IsVisible = false;
SetNearbyBusinesses();
NearbyLocationsView.ItemsSource = NearbyBusinesses;
NoLocationsView.ItemsSource = UserMessages;
SetViewVisibility();
_analyticsService.RecordEvent("Refresh Event: Refresh nearby businesses", AnalyticsEventCategory.UserAction);
}
We refactored more of that code but adding async was what got it working.
I hope this helps someone else save some time.

How to set up GCController valueChangeHandler Properly in Xcode?

I have successfully connected a steel series Nimbus dual analog controller to use for testing in both my iOS and tvOS apps. But I am unsure about how to properly set up the valueChangeHandler portion of my GCController property.
I understand so far that there are microGamepad, gamepad and extendedGamepad classes of controllers and the differences between them. I also understand that you can check to see if the respective controller class is available on the controller connected to your device.
But now I am having trouble setting up valueChangeHandler because if I set the three valueChangeHandler portions like so, then only the valueChangeHandler that works is the last one that was loaded in this sequence:
self.gameController = GCController.controllers()[0]
self.gameController.extendedGamepad?.valueChangedHandler = { (gamepad, element) -> Void in
if element == self.gameController.extendedGamepad?.leftThumbstick {
//Never gets called
}
}
self.gameController.gamepad?.valueChangedHandler = { (gamepad, element) -> Void in
if element == self.gameController.gamepad?.dpad {
//Never gets called
}
}
self.gameController.microGamepad?.valueChangedHandler = { (gamepad, element) -> Void in
if element == self.gameController.microGamepad?.dpad {
//Gets called
}
}
If I switch them around and call self.gameController.extendedGamepad.valueChangeHandler... last, then those methods will work and the gamepad and microGamepad methods will not.
Anyone know how to fix this?
You test which profile is available and depending on the profile, you set the valueChangedHandler.
It's important to realise that the extendedGamepad contains most functionality and the microGamepad least (I think the microGamepad is only used for the AppleTV remote). Therefore the checks should be ordered differently. An extendedGamepad has all functionality of the microGamepad + additional controls so in your code the method would always enter the microGamepad profile.
Apple uses the following code in the DemoBots example project:
private func registerMovementEvents() {
/// An analog movement handler for D-pads and movement thumbsticks.
let movementHandler: GCControllerDirectionPadValueChangedHandler = { [unowned self] _, xValue, yValue in
// Code to handle movement here ...
}
#if os(tvOS)
// `GCMicroGamepad` D-pad handler.
if let microGamepad = gameController.microGamepad {
// Allow the gamepad to handle transposing D-pad values when rotating the controller.
microGamepad.allowsRotation = true
microGamepad.dpad.valueChangedHandler = movementHandler
}
#endif
// `GCGamepad` D-pad handler.
// Will never enter here in case of AppleTV remote as the AppleTV remote is a microGamepad
if let gamepad = gameController.gamepad {
gamepad.dpad.valueChangedHandler = movementHandler
}
// `GCExtendedGamepad` left thumbstick.
if let extendedGamepad = gameController.extendedGamepad {
extendedGamepad.leftThumbstick.valueChangedHandler = movementHandler
}
}

Is there a way to rearrange objects on a DevX report?

I want to enable user to move things around on a devexpress print preview and print it only after it is done. If it is possible, could I get some directions where I can start looking? (I will not have the time to look into the whole documentation, what may sound lazy, but devx is kinda huge for the short time I have.)
I don't think you could do this on the Print preview directly, but what you could do is provide a button which launches the XtraReports Designer and pass in the layout from your currently displayed document. When the user has finished editing then you can reload the document in the print preview, loading its new layout as required. You may need to customize the designer heavily to remove various options restricting the user to only editing certain aspects - you can hide much of the functionality including data source, component tray etc:
designer video
designer documentation
hide options in designer
if(EditLayout(document))
RefreshDocument();
public static bool EditLayout(XtraReport document)
{
using (var designer = new XRDesignRibbonForm())
{
designer.OpenReport(document);
XRDesignPanel activePanel = designer.ActiveDesignPanel;
activePanel.AddCommandHandler(new DesignerCommandHandler(activePanel));
HideDesignerOptions(activePanel);
designer.ShowDialog();
changesMade = activePanel.Tag != null && (DialogResult)activePanel.Tag == DialogResult.Yes; //set this tag in your DesignerCommandHandler
activePanel.CloseReport();
}
return changesMade;
}
Finally, some utility methods for changing a document/reports layout:
internal static byte[] GetLayoutData(this XtraReport report)
{
using (MemoryStream mem = new MemoryStream())
{
report.SaveLayoutToXml(mem);
return mem.ToArray();
}
}
internal static void SetLayoutData(this XtraReport report, byte[] data)
{
using (var mem = new MemoryStream(data))
{
report.LoadLayoutFromXml(mem);
}
}

Flash CS5 to iOS - Using Filesystem causeing app not to run on iPhone

So I'm in the final stages of designing a game for the iPhone using Flash CS5 and I'm running into a problem with it running on the iPhone. I am a registered Developer, so I'm doing this completely legit... I'm just doing it on a PC and with Flash so there are a few workarounds being done lol. I'm at a point where it runs perfectly in the simulator but not on the iPhone itself. I load up the game and the opening screen loads up where I have my PLAY button located. The problem is, it doesn't work. It doesn't click, change screens... nada. So I worked it out to be something I'm doing wrong with the way I'm using the Filesystem and my save game file. If I take out the filestream stuff and just put in hard values into all my save game variables, the game runs just fine. The PLAY button does its thing and the game is good to go, except of course that I'm stuck to just the hard set values instead of being able to do little things like change levels. So here is the code I'm using, and hopefully someone can see where I've gone wrong.
The app starts off with running a main.as which calls the saved game file...
private function addedMain(event:Event):void {
//Set up opening screen
gameStart = new GameStart(this);
addChild(gameStart);
//set up the save game info
_savedGame = new SaveGameFile();
_savedGame.initGame();
}
gameStart runs a simple script that just has the title screen with a PLAY button. Clicking the PLAY button removes it and loads up the next screen. This is what isn't working right now. The system may be just freezing or something, at this point I don't have a way to tell exactly what's happening except that the PLAY button does nothing. So the next thing called is the saved game file itself:
package {
import flash.events.Event;
import flash.display.MovieClip;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.filesystem.File;
import flash.errors.IOError;
public class SaveGameFile extends MovieClip {
private var file:File;
private var savedGame:XML;
public function SaveGameFile() {
}
public function initGame():void {
file = File.applicationStorageDirectory;
file = file.resolvePath("savedGame.xml");
xmlLoad();
initVariables();
}
private function xmlLoad():void {
var fileStream:FileStream = new FileStream();
try {
fileStream.open(file, FileMode.READ);
} catch(e:IOError) {
createSaveFile();
xmlSave();
}
var str:String = fileStream.readMultiByte(file.size, File.systemCharset);
savedGame = XML(str);
fileStream.close();
}
public function saveTheGame():void {
xmlSave();
}
private function xmlSave():void {
var writeStream:FileStream = new FileStream();
writeStream.open(file, FileMode.WRITE);
var s:String = String(savedGame);
writeStream.writeMultiByte(s, File.systemCharset);
writeStream.close();
}
private function createSaveFile():void {
savedGame =
<savedGame>
<levelsCompleted>0</levelsCompleted>
<levelWorkingOn>0</levelWorkingOn>
<volLevel>0.05</volLevel>
<sfxLevel>0.75</sfxLevel>
<level>
<levelNum>1</levelNum>
<highScore>0</highScore> //...etc.
</level>
//you get the idea
</savedGame>
}
So as you can see, I'm creating a XML file called savedGame (created the first time the game runs and then is just loaded when the game runs) and from that I'm pulling all my level information as needed and saving scores and such. Again this is working just fine in the simulator (of course) but if I leave this code in, I don't get past the starting page. So does anything jump out to anyone as to why this isn't working? Help! Thanks ;)
Adobe a while ago published a few articles on programming AIR apps for iOs devices, maybe this one will help you out (in the unlikely case you didn't see it already):
Saving state in AIR applications for iOS devices
UPDATE: So I may have been on the wrong track all along because through messing around with my file on my computer I was finally able to see the error occur where I could easily trace problems compared to tracing on the iPhone. I don't know if my original code was flawed or not, however, I do know that this code below works perfectly, so I'm keeping it (and I like it better anyway). HOWEVER, my main problem seems to have been the fact that I'm using XML as my save file. This is perfectly fine to do, BUT for some reason, saving the file causes a couple of strange characters to be entered at the very beginning of the file. So instead of starting
<savedGame>
it was starting with something like
|Y<savedGame>
The | was actually a symbol I couldn't find that had a line in the middle and a charCode of 25 and the Y was actually a Yen sign. So whatever that was, it was causing me to get a 1088 Error. I solved this by striping any and all characters (whatever they end up being just in case it's different for everyone... not sure if it is but not taking any chances). So in my code below I added a section between //EDIT START and //EDIT STOP that is only needed if you too are using XML in your script. Otherwise you can leave this out. Hope this helps :)
ORIGINAL POST: OK so it seems that through some messing around with the code listed at the bottom of the page linked by danii, I was able to get that sites code working after all. I'm tempted to look back at my original code and mess with it to see if I can get it to work with what I've learned from this, but the game is working so I'm moving on and never looking back... lol. What I did find out though is that for my needs, that pages code was not quite workable (and why I gave up on it originally). It might have worked for their needs, but unless you package the game with a file, as far as I can tell their function "getSaveStream" will always return 'null' because the file will never be able to be written. So I removed a lot of their if's and try's because they were causing the game to fail. Here is what I ended up with in the context of my original post:
package {
import flash.events.Event;
import flash.display.MovieClip;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.filesystem.File;
import flash.errors.IOError;
public class SaveGameFile extends MovieClip {
private var f:File;
private var savedGame:XML;
public function SaveGameFile() {
}
public function initGame():void {
xmlLoad();
initVariables();
}
private function getSaveStream(write:Boolean, sync:Boolean = true):FileStream {
// The data file lives in the app storage directory, per iPhone guidelines.
f = File.applicationStorageDirectory.resolvePath("savedGame.xml");
// Try creating and opening the stream.
var fs:FileStream = new FileStream();
try {
//write is True and writing asynchronously.
if(write && !sync) {
fs.openAsync(f, FileMode.WRITE);
} else {
//For synchronous write, or all reads, open synchronously.
fs.open(f, write ? FileMode.WRITE : FileMode.READ);
}
} catch(e:Error) {
// On error return null.
return null;
}
return fs;
}
private function xmlLoad():void {
var fs:FileStream = getSaveStream(false);
if(fs) {
var str:String = fs.readMultiByte(f.size, File.systemCharset);
//EDIT START
//check for "bad characters" at the start of the XML file
var badCharCheck = str.charCodeAt(0);
//the first char MUST be '<' which has a CharCode of 60
while(badCharCheck != 60) {
//if the first character isn't '<' then remove it
str = str.replace(String.fromCharCode(badCharCheck), "") ;
//recheck the first character
badCharCheck = str.charCodeAt(0);
}
//EDIT END
savedGame = XML(str);
fs.close();
} else {
//fs returned null so create the XML file savedGame and save it
createSaveFile();
xmlSave();
}
}
public function saveTheGame():void {
//this is used to call the save function externally
xmlSave();
}
private function xmlSave():void {
//the file exists because it was created with FileMode.WRITE in getSaveStream
var fs:FileStream = getSaveStream(true, false);
var s:String = String(savedGame);
fs.writeUTF(s);
fs.close();
}
private function createSaveFile():void {
//this is only called the very first time the game is run on the iPhone
savedGame =
<savedGame>
<levelsCompleted>0</levelsCompleted>
<levelWorkingOn>0</levelWorkingOn>
<volLevel>0.05</volLevel>
<sfxLevel>0.75</sfxLevel>
<level>
<levelNum>1</levelNum>
<highScore>0</highScore> //...etc.
</level>
//you get the idea
</savedGame>
}
So I'm happy to say it tested perfectly and is now running and saving information like it is supposed to. Hope this is of use to someone who runs into the same issue I did. Happy coding!

xpcom/jetpack observe all document loads

I write a Mozilla Jetpack based add-on that has to run whenever a document is loaded. For "toplevel documents" this mostly works using this code (OserverService = require('observer-service')):
this.endDocumentLoadCallback = function (subject, data) {
console.log('loaded: '+subject.location);
try {
server.onEndDocumentLoad(subject);
}
catch (e) {
console.error(formatTraceback(e));
}
};
ObserverService.add("EndDocumentLoad", this.endDocumentLoadCallback);
But the callback doesn't get called when the user opens a new tab using middle click or (more importantly!) for frames. And even this topic I only got through reading the source of another extension and not through the documentation.
So how do I register a callback that really gets called every time a document is loaded?
Edit: This seems to do what I want:
function callback (event) {
// this is the content document of the loaded page.
var doc = event.originalTarget;
if (doc instanceof Ci.nsIDOMNSHTMLDocument) {
// is this an inner frame?
if (doc.defaultView.frameElement) {
// Frame within a tab was loaded.
console.log('!!! loaded frame:',doc.location.href);
}
else {
console.log('!!! loaded top level document:',doc.location.href);
}
}
}
var wm = Cc["#mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
var mainWindow = wm.getMostRecentWindow("navigator:browser");
mainWindow.gBrowser.addEventListener("load", callback, true);
Got it partially from here: https://developer.mozilla.org/en/XUL_School/Intercepting_Page_Loads
#kizzx2 you are better served with #jetpack
To the original question: why don't you use tab-browser module. Something like this:
var browser = require("tab-browser");
exports.main = function main(options, callbacks) {
initialize(function (config) {
browser.whenContentLoaded(
function(window) {
// something to do with the window
// e.g., if (window.locations.href === "something")
}
);
});
Much cleaner than what you do IMHO and (until we have official pageMods module) the supported way how to do this.
As of Addon SDK 1.0, the proper way to do this is to use the page-mod module.
(Under the hood it's implemented using the document-element-inserted observer service notification, you can use it in a regular extension or if page-mod doesn't suit you.)

Resources