Shikamu
Shikamu

Reputation: 381

iOS printing to a printer on the network

I'm trying to make an iOS application that can "automatically" print pictures to a printer that's on the network. I'm fairly new to iOS and Qt but so far I've not been able to find out how to do the printing. I found some examples that show me how I can use bonjour (https://doc.qt.io/archives/qq/qq23-bonjour.html), I think I can more or less find my printer like this but I couldn't find any help so far on how to use QPrinter to automatically connect to that printer. Basically my code so far is essentially:

QPrinter printer;
printer.setOutputFormat(QPrinter::NativeFormat);
printer.setPrinterName("Canon SELPHY CP1200");
QPainter painter;
if(!painter.begin(&printer))
{
  //failed, we actually hit this at the moment
  return 1;
}
painter.drawText(10, 10, "Test !");
painter.end();

I think the problem is that I need to somehow connect the device to my Canon printer somehow but I don't know how to do that. Any insight would be greatly appreciated :)

Cheers!

Upvotes: 1

Views: 694

Answers (1)

Laurent Michel
Laurent Michel

Reputation: 1321

Even though Qt's documentation states that its printing API is working on all platforms, see e.g. Qt print support, it seems that it doesn't hold true for iOS. Instead, it appears you need to go for the UIPrintInteractionController solution. It is fairly easy to do. Let me provide you with a simple example. Let me assume that you chose Qt because you want your application to be portable and that you have a dedicated class PrinterManager that handles printing in your framework:

// this is PrinterManager.hpp
class PrinterManagerImpl;

class PrinterManager : public QObject
{
  Q_OBJECT

public:
  PrinterManager(QObject* a_Parent = Q_NULLPTR);
  virtual ~PrinterManager();

  Q_INVOKABLE void setupPrinter();
  Q_INVOKABLE void print(const QString& a_PathToImg) const;

private:
  Q_DISABLE_COPY(PrinterManager)

private:
  std::unique_ptr<PrinterManagerImpl> m_Impl;
};

along with the following implementation:

// this is PrinterManager.cpp
#include "PrinterManager.hpp"
#include "PrinterManagerImpl.hpp"

#ifndef Q_OS_IOS
PrinterManager::PrinterManager(QObject* a_Parent)
  : QObject(a_Parent)
  , m_Impl(new MyStandardPrinterManagerImpl) // implementation for any case except iOS
{ }
#endif

PrinterManager::~PrinterManager()
{ }

void PrinterManager::setupPrinter()
{
  m_Impl->setupPrinter();
}

void PrinterManager::print(const QString& a_PathToImg) const
{
  m_Impl->print(a_PathToImg);
}

Specified like this, the PrinterManager can be easily be used in QML.

The whole trick consists in writing your PrinterManager with the PIMPL idiom, i.e. the encapsulation of your class implementation in the member m_Impl. Then, based on the platform you compile your code for, you will provide whatever PrinterManagerImpl is necessary.

Along with the above PrinterManager implementation (cpp file), you need to define the following mm file (for objective-c++ code), which contains the ios-specific implementation of PrinterManager's constructor:

// this is PrinterManager.mm
#import "PrinterManager.hpp"
#import "IosPrinterManagerImpl.hpp"

PrinterManager::PrinterManager(QObject* a_Parent)
  : QObject(a_Parent)
  , m_Impl(new IosPrinterManagerImpl()) // special iOS implementation
{ }

When you compile this code, both the mm and cpp files are considered (if you follow the advice below on your project's .pro file). When you compile it for iOS, no constructor implementation is found in the cpp file. An implementation can be found in the mm file where we define the IosPrinterManagerImpl. Let's have a look at the PrinterManagerImpl:

// this is PrinterManagerImpl.hpp
class PrinterManagerImpl
{
  public:
    PrinterManagerImpl() { }
    virtual ~PrinterManagerImpl() { }

    virtual void setupPrinter() = 0;
    virtual void print(const QString& a_PathToImg) const = 0;

  private:
    Q_DISABLE_COPY(PrinterManagerImpl)
};

The IosPrinterManagerImpl looks like this (inspired from the end of this video and this advice):

// IosPrinterManagerImpl.hpp
#import "PrinterManagerImpl.hpp"
#import <UIKit/UIPrinter.h>

class QWidget;

class IosPrinterManagerImpl : public PrinterManagerImpl
{
public:
  IosPrinterManagerImpl();
  virtual ~IosPrinterManagerImpl();

  virtual void setupPrinter() override;
  virtual void print(const QString& a_PathToImg) const override;

private:
  UIPrinter* m_Printer;
  QWidget* m_Dialog;
};

Its implementation is

// IosPrinterManagerImpl.mm
#import "IosPrinterManagerImpl.hpp"

#import <QApplication>
#import <QWidget>
#import <QWindow>

#import <UIKit/UIPrinterPickerController.h>
#import <UIKit/UIPrintInteractionController.h>
#import <UIKit/UIPrintInfo.h>

IosPrinterManagerImpl::IosPrinterManagerImpl()
  : PrinterManagerImpl()
  , m_Dialog(new QWidget(QApplication::activeWindow()))
{
  m_Dialog->setGeometry(0, 0, 100, 100);
}

IosPrinterManagerImpl::~IosPrinterManagerImpl()
{ }

void IosPrinterManagerImpl::setupPrinter()
{
  // this displays an UI where you can select the printer you want from your local network
  auto picker = [UIPrinterPickerController printerPickerControllerWithInitiallySelectedPrinter:m_Printer];

  if(auto view = reinterpret_cast<UIView*>(m_Dialog->window()->winId()))
  {
    [picker presentFromRect:view.bounds inView:view animated:YES
            completionHandler:^(UIPrinterPickerController* controller, BOOL userDidSelect, NSError* /*err*/)
            {
              if(userDidSelect)
              {
                m_Printer = controller.selectedPrinter;
              }
            }

    ];
  }
}

void IosPrinterManagerImpl::print(const QString& a_PathToImg) const
{
  auto printInfo([UIPrintInfo printInfo]);
  printInfo.jobName = @"Test";
  printInfo.outputType = UIPrintInfoOutputPhoto;

  auto imgUrl([NSURL fileURLWithPath:a_PathToImg.toNSString()]);
  auto canPrint([UIPrintInteractionController canPrintURL: imgUrl]);

  auto controller = [UIPrintInteractionController sharedPrintController];
  if(controller && canPrint)
  {
    controller.printInfo = printInfo;
    controller.printingItem = imgUrl;
    // this allows your app to directly print to the selected printer
    [controller printToPrinter: m_Printer
                completionHandler: ^(UIPrintInteractionController* /*printCtrl*/, BOOL completed, NSError* err)
    {
      if(completed && !err)
      {
        qInfo() << "Print successful";
      }
    }];
  }
}

In your pro file, you need to add the above objective-c++ files in the following way:

ios {
  OBJECTIVE_SOURCES += $${PRINTERMANAGER_FOLDER}/PrinterManager.mm \
    $${PRINTERMANAGER_FOLDER}/IosPrinterManagerImpl.mm \
    $${PRINTERMANAGER_FOLDER}/IosPrinterManagerImpl.hpp
}

I am not an expert Objective-C++ code writer. For instance, I am pretty sure you can integrate the printer picker widget created in the setupPrinter() method in a more clever way to your Qt GUI. This code, however, probably solves your problem ...

Upvotes: 1

Related Questions